BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[mono.git] / mcs / class / System.Data / System.Data / DataTableReader.cs
1 //
2 // System.Data.DataTableReader.cs
3 //
4 // Author:
5 //   Sureshkumar T      <tsureshkumar@novell.com>
6 //   Tim Coleman        (tim@timcoleman.com)
7 //
8 // Copyright (C) Tim Coleman, 2003
9 //
10
11 //
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 #if NET_2_0
35
36 using System.Collections;
37 using System.Data.Common;
38 using System.ComponentModel;
39 using System.Text.RegularExpressions;
40
41 namespace System.Data {
42         public sealed class DataTableReader : DbDataReader {
43                 bool            _closed;
44                 DataTable []    _tables;
45                 int             _current = -1;
46                 int             _index;
47                 DataTable       _schemaTable;
48                 bool            _tableCleared = false;
49                 bool            _subscribed = false;
50                 DataRow         _rowRef;
51                 bool            _schemaChanged = false;
52
53                 #region Constructors
54
55                 public DataTableReader (DataTable dt)
56                         : this (new DataTable[] {dt})
57                 {
58                 }
59
60                 public DataTableReader (DataTable[] dataTables)
61                 {
62                         if (dataTables == null || dataTables.Length <= 0)
63                                 throw new ArgumentException ("Cannot Create DataTable. Argument Empty!");
64
65                         this._tables = new DataTable [dataTables.Length];
66
67                         for (int i = 0; i < dataTables.Length; i++)
68                                 this._tables [i] = dataTables [i];
69
70                         _closed = false;
71                         _index = 0;
72                         _current = -1;
73                         _rowRef = null;
74                         _tableCleared = false;
75
76                         SubscribeEvents ();
77                 }
78
79                 #endregion // Constructors
80
81                 #region Properties
82
83                 public override int Depth {
84                         get { return 0; }
85                 }
86
87                 public override int FieldCount {
88                         get { return CurrentTable.Columns.Count; }
89                 }
90
91                 public override bool HasRows {
92                         get { return CurrentTable.Rows.Count > 0; }
93                 }
94
95                 public override bool IsClosed {
96                         get { return _closed; }
97                 }
98
99                 public override object this [int index] {
100                         get {
101                                 Validate ();
102                                 if (index < 0 || index >= FieldCount)
103                                         throw new ArgumentOutOfRangeException ("index " + index + " is not in the range");
104                                 DataRow row = CurrentRow;
105                                 if (row.RowState == DataRowState.Deleted)
106                                         throw new InvalidOperationException ("Deleted Row's information cannot be accessed!");
107                                 return row [index];
108                         }
109                 }
110
111                 private DataTable CurrentTable {
112                         get { return _tables [_index]; }
113                 }
114
115                 private DataRow CurrentRow {
116                         get { return (DataRow) CurrentTable.Rows [_current]; }
117                 }
118
119
120                 public override object this [string name] {
121                         get {
122                                 Validate ();
123                                 DataRow row = CurrentRow;
124                                 if (row.RowState == DataRowState.Deleted)
125                                         throw new InvalidOperationException ("Deleted Row's information cannot be accessed!");
126                                 return row [name];
127                         }
128                 }
129
130                 public override int RecordsAffected {
131                         get { return 0; }
132                 }
133
134                 #endregion // Properties
135
136                 #region Methods
137
138                 private void SubscribeEvents ()
139                 {
140                         if (_subscribed)        // avoid subscribing multiple times
141                                 return;
142                         CurrentTable.TableCleared += new DataTableClearEventHandler (OnTableCleared);
143                         CurrentTable.RowChanged += new DataRowChangeEventHandler (OnRowChanged);
144                         CurrentTable.Columns.CollectionChanged += new CollectionChangeEventHandler (OnColumnCollectionChanged);
145                         for (int i=0; i < CurrentTable.Columns.Count; ++i)
146                                 CurrentTable.Columns [i].PropertyChanged += new PropertyChangedEventHandler (OnColumnChanged);
147                         _subscribed = true;
148                         _schemaChanged = false;
149                 }
150
151                 private void UnsubscribeEvents ()
152                 {
153                         if (!_subscribed)       // avoid un-subscribing multiple times
154                                 return;
155                         CurrentTable.TableCleared -= new DataTableClearEventHandler (OnTableCleared);
156                         CurrentTable.RowChanged -= new DataRowChangeEventHandler (OnRowChanged);
157                         CurrentTable.Columns.CollectionChanged -= new CollectionChangeEventHandler (OnColumnCollectionChanged);
158                         for (int i=0; i < CurrentTable.Columns.Count; ++i)
159                                 CurrentTable.Columns [i].PropertyChanged -= new PropertyChangedEventHandler (OnColumnChanged);
160                         _subscribed = false;
161                         _schemaChanged = false;
162                 }
163
164                 public override void Close ()
165                 {
166                         if (IsClosed)
167                                 return;
168
169                         UnsubscribeEvents ();
170                         _closed = true;
171                 }
172
173                 public override bool GetBoolean (int i)
174                 {
175                         return (bool) GetValue (i);
176                 }
177
178                 public override byte GetByte (int i)
179                 {
180                         return (byte) GetValue (i);
181                 }
182
183                 public override long GetBytes (int i, long dataIndex, byte[] buffer, int bufferIndex, int length)
184                 {
185                         byte[] value = this [i] as byte[];
186                         if (value == null)
187                                 ThrowInvalidCastException (this [i].GetType (), typeof (byte[]));
188                         if (buffer == null)
189                                 return value.Length;
190                         int copylen = length > value.Length ? value.Length : length;
191                         Array.Copy (value, dataIndex, buffer, bufferIndex, copylen);
192                         return copylen;
193                 }
194
195                 public override char GetChar (int i)
196                 {
197                         return (char) GetValue (i);
198                 }
199
200                 public override long GetChars (int i, long dataIndex, char[] buffer, int bufferIndex, int length)
201                 {
202                         char[] value = this [i] as char[];
203                         if (value == null)
204                                 ThrowInvalidCastException (this [i].GetType (), typeof (char[]));
205                         if (buffer == null)
206                                 return value.Length;
207                         int copylen = length > value.Length ? value.Length : length;
208                         Array.Copy (value, dataIndex, buffer, bufferIndex, copylen);
209                         return copylen;
210                 }
211
212                 public override string GetDataTypeName (int i)
213                 {
214                         return GetFieldType (i).ToString ();
215                 }
216
217                 public override DateTime GetDateTime (int i)
218                 {
219                         return (DateTime) GetValue (i);
220                 }
221
222                 public override decimal GetDecimal (int i)
223                 {
224                         return (decimal) GetValue (i);
225                 }
226
227                 public override double GetDouble (int i)
228                 {
229                         return (double) GetValue (i);
230                 }
231
232                 public override IEnumerator GetEnumerator ()
233                 {
234                         return new DbEnumerator (this);
235                 }
236
237                 public override Type GetProviderSpecificFieldType (int i)
238                 {
239                         return GetFieldType (i);
240                 }
241
242                 public override Type GetFieldType (int i)
243                 {
244                         ValidateClosed ();
245                         return CurrentTable.Columns [i].DataType;
246                 }
247
248                 public override float GetFloat (int i)
249                 {
250                         return (float) GetValue (i);
251                 }
252
253                 public override Guid GetGuid (int i)
254                 {
255                         return (Guid) GetValue (i);
256                 }
257
258                 public override short GetInt16 (int i)
259                 {
260                         return (short) GetValue (i);
261                 }
262
263                 public override int GetInt32 (int i)
264                 {
265                         return (int) GetValue (i);
266                 }
267
268                 public override long GetInt64 (int i)
269                 {
270                         return (long) GetValue (i);
271                 }
272
273                 public override string GetName (int i)
274                 {
275                         ValidateClosed ();
276                         return CurrentTable.Columns [i].ColumnName;
277                 }
278
279                 public override int GetOrdinal (string name)
280                 {
281                         ValidateClosed ();
282                         int index = CurrentTable.Columns.IndexOf (name);
283                         if (index == -1)
284                                 throw new ArgumentException (String.Format ("Column {0} is not found in the schema", name));
285                         return index;
286                 }
287
288                 public override object GetProviderSpecificValue (int i)
289                 {
290                         return GetValue (i);
291                 }
292
293                 public override int GetProviderSpecificValues (object[] values)
294                 {
295                         return GetValues (values);
296                 }
297
298                 public override string GetString (int i)
299                 {
300                         return (string) GetValue (i);
301                 }
302
303                 public override object GetValue (int i)
304                 {
305                         return this [i];
306                 }
307
308                 public override int GetValues (object[] values)
309                 {
310                         Validate ();
311                         if (CurrentRow.RowState == DataRowState.Deleted)
312                                 throw new DeletedRowInaccessibleException ("");
313
314                         int count = (FieldCount < values.Length ? FieldCount : values.Length);
315                         for (int i=0; i < count; ++i)
316                                 values [i] = CurrentRow [i];
317                         return count;
318                 }
319
320                 public override bool IsDBNull (int i)
321                 {
322                         return GetValue (i) is DBNull;
323                 }
324
325                 public override DataTable GetSchemaTable ()
326                 {
327                         ValidateClosed ();
328                         ValidateSchemaIntact ();
329
330                         if (_schemaTable != null)
331                                 return _schemaTable;
332
333                         DataTable dt = new DataTable ();
334                         dt.Columns.Add ("ColumnName", typeof (string));
335                         dt.Columns.Add ("ColumnOrdinal", typeof (int));
336                         dt.Columns.Add ("ColumnSize", typeof (int));
337                         dt.Columns.Add ("NumericPrecision", typeof (short));
338                         dt.Columns.Add ("NumericScale", typeof (short));
339                         dt.Columns.Add ("DataType", typeof (Type));
340                         dt.Columns.Add ("ProviderType", typeof (int));
341                         dt.Columns.Add ("IsLong", typeof (bool));
342                         dt.Columns.Add ("AllowDBNull", typeof (bool));
343                         dt.Columns.Add ("IsReadOnly", typeof (bool));
344                         dt.Columns.Add ("IsRowVersion", typeof (bool));
345                         dt.Columns.Add ("IsUnique", typeof (bool));
346                         dt.Columns.Add ("IsKey", typeof (bool));
347                         dt.Columns.Add ("IsAutoIncrement", typeof (bool));
348                         dt.Columns.Add ("BaseCatalogName", typeof (string));
349                         dt.Columns.Add ("BaseSchemaName", typeof (string));
350                         dt.Columns.Add ("BaseTableName", typeof (string));
351                         dt.Columns.Add ("BaseColumnName", typeof (string));
352                         dt.Columns.Add ("AutoIncrementSeed", typeof (Int64));
353                         dt.Columns.Add ("AutoIncrementStep", typeof (Int64));
354                         dt.Columns.Add ("DefaultValue", typeof (object));
355                         dt.Columns.Add ("Expression", typeof (string));
356                         dt.Columns.Add ("ColumnMapping", typeof (MappingType));
357                         dt.Columns.Add ("BaseTableNamespace", typeof (string));
358                         dt.Columns.Add ("BaseColumnNamespace", typeof (string));
359
360                         DataRow row;
361                         DataColumn col;
362                         for (int i=0; i < CurrentTable.Columns.Count; ++i) {
363                                 row = dt.NewRow ();
364                                 col = CurrentTable.Columns [i];
365                                 row ["ColumnName"] = col.ColumnName;
366                                 row ["BaseColumnName"] = col.ColumnName;
367                                 row ["ColumnOrdinal"] = col.Ordinal;
368                                 row ["ColumnSize"] = col.MaxLength;
369                                 // ms.net doesent set precision and scale even for Decimal values
370                                 // when are these set ?
371                                 row ["NumericPrecision"] = DBNull.Value;
372                                 row ["NumericScale"] = DBNull.Value;
373                                 row ["DataType"] = col.DataType;
374                                 row ["ProviderType"] = DBNull.Value; //col.ProviderType;
375                                 // ms.net doesent set this when datatype is string and maxlength = -1
376                                 // when is this set ?
377                                 row ["IsLong"] = false;
378                                 row ["AllowDBNull"] = col.AllowDBNull;
379                                 row ["IsReadOnly"] = col.ReadOnly;
380                                 row ["IsRowVersion"] = false; //this is always false
381                                 row ["IsUnique"] = col.Unique;
382                                 row ["IsKey"] = (Array.IndexOf (CurrentTable.PrimaryKey, col) != -1) ;
383                                 row ["IsAutoIncrement"]= col.AutoIncrement;
384                                 row ["AutoIncrementSeed"] = col.AutoIncrementSeed;
385                                 row ["AutoIncrementStep"] = col.AutoIncrementStep;
386                                 row ["BaseCatalogName"] = (CurrentTable.DataSet != null ? CurrentTable.DataSet.DataSetName : null);
387                                 row ["BaseSchemaName"] = DBNull.Value; // this is always null
388                                 row ["BaseTableName"] = CurrentTable.TableName;
389                                 row ["DefaultValue"] = col.DefaultValue;
390                                 // If col expression depends on any external table , then the
391                                 // Expression value is set to empty string in the schematable.
392                                 if (col.Expression == "")
393                                         row ["Expression"] = col.Expression;
394                                 else {
395                                         Regex reg = new Regex ("((Parent|Child)( )*[.(])", RegexOptions.IgnoreCase);
396                                         if (reg.IsMatch (col.Expression, 0))
397                                                 row ["Expression"] = DBNull.Value;
398                                         else
399                                                 row ["Expression"] = col.Expression;
400                                 }
401
402                                 row ["ColumnMapping"] = col.ColumnMapping;
403                                 row ["BaseTableNamespace"] = CurrentTable.Namespace;
404                                 row ["BaseColumnNamespace"] = col.Namespace;
405                                 dt.Rows.Add (row);
406                         }
407
408                         return _schemaTable = dt;
409                 }
410
411                 private void Validate ()
412                 {
413                         ValidateClosed ();
414
415                         if (_index >= _tables.Length)
416                                 throw new InvalidOperationException ("Invalid attempt to read when no data is present");
417                         if (_tableCleared)
418                                 throw new RowNotInTableException ("The table is cleared, no rows are accessible");
419                         if (_current == -1)
420                                 throw new InvalidOperationException ("DataReader is invalid for the DataTable");
421                         ValidateSchemaIntact ();
422                 }
423
424                 private void ValidateClosed ()
425                 {
426                         if (IsClosed)
427                                 throw new InvalidOperationException ("Invalid attempt to read when the reader is closed");
428                 }
429
430                 private void ValidateSchemaIntact ()
431                 {
432                         if (_schemaChanged)
433                                 throw new InvalidOperationException ("Schema of current DataTable '" + CurrentTable.TableName +
434                                                 "' in DataTableReader has changed, DataTableReader is invalid.");
435                 }
436
437                 void ThrowInvalidCastException (Type sourceType, Type destType)
438                 {
439                         throw new InvalidCastException (
440                                 String.Format ("Unable to cast object of type '{0}' to type '{1}'.", sourceType, destType));
441                 }
442
443                 private bool MoveNext ()
444                 {
445                         if (_index >= _tables.Length || _tableCleared)
446                                 return false;
447
448                         do {
449                                 _current++;
450                         } while (_current < CurrentTable.Rows.Count && CurrentRow.RowState == DataRowState.Deleted);
451
452                         _rowRef = _current < CurrentTable.Rows.Count ? CurrentRow : null;
453
454                         return _current < CurrentTable.Rows.Count;
455
456                 }
457
458                 public override bool NextResult ()
459                 {
460                         if ((_index + 1) >= _tables.Length) {
461                                 UnsubscribeEvents ();
462                                 _index = _tables.Length;     // to make any attempt invalid
463                                 return false; // end of tables.
464                         }
465
466                         UnsubscribeEvents ();
467                         _index++;
468                         _current = -1;
469                         _rowRef = null;
470                         _schemaTable = null;            // force to create fresh
471                         _tableCleared = false;
472                         SubscribeEvents ();
473                         return true;
474                 }
475
476                 public override bool Read ()
477                 {
478                         ValidateClosed ();
479                         return MoveNext ();
480                 }
481
482                 #endregion // Methods
483
484                 #region // Event Handlers
485
486                 private void OnColumnChanged (object sender, PropertyChangedEventArgs args)
487                 {
488                         _schemaChanged = true;
489                 }
490
491                 private void OnColumnCollectionChanged (object sender, CollectionChangeEventArgs args)
492                 {
493                         _schemaChanged = true;
494                 }
495
496                 private void OnRowChanged (object src, DataRowChangeEventArgs args)
497                 {
498                         DataRowAction action = args.Action;
499                         DataRow row = args.Row;
500                         if (action == DataRowAction.Add) {
501                                 if (_tableCleared && _current != -1)
502                                         return;
503
504                                 if (_current == -1 || (_current >= 0 && row.RowID > CurrentRow.RowID)) {
505                                         _tableCleared = false;
506                                         return; // no. movement required, if row added after current.
507                                 }
508
509                                 _current++;
510                                 _rowRef = CurrentRow;
511                         }
512
513                         if (action == DataRowAction.Commit && row.RowState == DataRowState.Detached) {
514                                 // if i am the row deleted, move one down
515                                 if (_rowRef == row) {
516                                         _current --;
517                                         _rowRef = _current >= 0 ? CurrentRow : null;
518                                 }
519
520                                 // if the row deleted is the last row, move down
521                                 if (_current >= CurrentTable.Rows.Count) {
522                                         _current--;
523                                         _rowRef = _current >= 0 ? CurrentRow : null;
524                                         return;
525                                 }
526
527                                 // deleting a row below _current moves the row one down
528                                 if (_current > 0 && _rowRef == CurrentTable.Rows [_current-1]) {
529                                         _current--;
530                                         _rowRef = CurrentRow;
531                                         return;
532                                 }
533                         }
534                 }
535
536                 private void OnTableCleared (object src, DataTableClearEventArgs args)
537                 {
538                         _tableCleared = true;
539                 }
540
541                 #endregion // Event Handlers
542         }
543 }
544
545 #endif // NET_2_0