2002-10-23 Ville Palo <vi64pa@koti.soon.fi>
[mono.git] / mcs / class / System.Data / System.Data.Odbc / OdbcDataReader.cs
1 //
2 // System.Data.Odbc.OdbcDataReader
3 //
4 // Author:
5 //   Brian Ritchie (brianlritchie@hotmail.com) 
6 //
7 // Copyright (C) Brian Ritchie, 2002
8 //
9
10 using System.Collections;
11 using System.ComponentModel;
12 using System.Data;
13 using System.Data.Common;
14
15 namespace System.Data.Odbc
16 {
17         public sealed class OdbcDataReader : MarshalByRefObject, IDataReader, IDisposable, IDataRecord, IEnumerable
18         {
19                 #region Fields
20                 
21                 private OdbcCommand command;
22                 private bool open;
23                 private int currentRow;
24                 private OdbcColumn[] cols;
25                 private IntPtr hstmt;
26                 private CommandBehavior behavior;
27
28                 #endregion
29
30                 #region Constructors
31
32                 internal OdbcDataReader (OdbcCommand command, CommandBehavior behavior) 
33                 {
34                         this.command = command;
35                         this.behavior=behavior;
36                         this.command.Connection.DataReader = this;
37                         open = true;
38                         currentRow = -1;
39                         hstmt=command.hStmt;
40                         // Init columns array;
41                         short colcount=0;
42                         libodbc.SQLNumResultCols(hstmt, ref colcount);
43                         cols=new OdbcColumn[colcount];
44                 }
45
46                 #endregion
47
48                 #region Properties
49
50                 public int Depth {
51                         get {
52                                 return 0; // no nested selects supported
53                         }
54                 }
55
56                 public int FieldCount {
57                         get {
58
59                         return cols.Length;
60                         }
61                 }
62
63                 public bool IsClosed {
64                         get {
65                                 return !open;
66                         }
67                 }
68
69                 public object this[string name] {
70                         get {
71                                 int pos;
72
73                                 if (currentRow == -1)
74                                         throw new InvalidOperationException ();
75
76                                 pos = ColIndex(name);
77                                 
78                                 if (pos == -1)
79                                         throw new IndexOutOfRangeException ();
80
81                                 return this[pos];
82                         }
83                 }
84
85                 public object this[int index] {
86                         get {
87                                 return (object) GetValue (index);
88                         }
89                 }
90
91                 public int RecordsAffected {
92                         get {
93                                 return -1;
94                         }
95                 }
96
97                 #endregion
98
99                 #region Methods
100                 
101                 private int ColIndex(string colname)
102                 {
103                         int i=0;
104                         foreach (OdbcColumn col in cols)
105                         {
106                                 if (col.ColumnName==colname)
107                                         return i;
108                                 i++;
109                         }
110                         return 0;
111                 }
112
113                 // Dynamically load column descriptions as needed.
114                 private OdbcColumn GetColumn(int ordinal)
115                 {
116                         if (cols[ordinal]==null)
117                         {
118                                 short bufsize=255;
119                                 byte[] colname_buffer=new byte[bufsize];
120                                 string colname;
121                                 short colname_size=0;
122                                 short ColSize=0, DecDigits=0, Nullable=0, dt=0;
123                                 OdbcReturn ret=libodbc.SQLDescribeCol(hstmt, Convert.ToUInt16(ordinal+1), 
124                                         colname_buffer, bufsize, ref colname_size, ref dt, ref ColSize, 
125                                         ref DecDigits, ref Nullable);
126                                 if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) 
127                                         throw new OdbcException(new OdbcError("SQLDescribeCol",OdbcHandleType.Stmt,hstmt));
128                                 colname=System.Text.Encoding.Default.GetString(colname_buffer);
129                                 colname=colname.Replace((char) 0,' ').Trim();
130                                 OdbcColumn c=new OdbcColumn(colname, (OdbcType) dt);
131                                 c.AllowDBNull=(Nullable!=0);
132                                 c.Digits=DecDigits;
133                                 if (c.IsStringType)
134                                         c.MaxLength=ColSize;
135                                 cols[ordinal]=c;
136                         }
137                         return cols[ordinal];
138                 }
139         
140                 public void Close ()
141                 {
142                         // libodbc.SQLFreeHandle((ushort) OdbcHandleType.Stmt, hstmt);
143                 
144                         OdbcReturn ret=libodbc.SQLCloseCursor(hstmt);
145                         if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) 
146                                 throw new OdbcException(new OdbcError("SQLCloseCursor",OdbcHandleType.Stmt,hstmt));
147         
148                         open = false;
149                         currentRow = -1;
150
151                         this.command.Connection.DataReader = null;
152
153                         if ((behavior & CommandBehavior.CloseConnection)==CommandBehavior.CloseConnection)
154                                 this.command.Connection.Close();
155                 }
156
157                 ~OdbcDataReader ()
158                 {
159                         if (open)
160                                 Close ();
161                 }
162
163                 public bool GetBoolean (int ordinal)
164                 {
165                         return (bool) GetValue(ordinal);
166                 }
167
168                 [MonoTODO]
169                 public byte GetByte (int ordinal)
170                 {
171                         throw new NotImplementedException ();
172                 }
173
174                 [MonoTODO]
175                 public long GetBytes (int ordinal, long dataIndex, byte[] buffer, int bufferIndex, int length)
176                 {
177                         throw new NotImplementedException ();
178                 }
179                 
180                 [MonoTODO]
181                 public char GetChar (int ordinal)
182                 {
183                         throw new NotImplementedException ();
184                 }
185
186                 [MonoTODO]
187                 public long GetChars (int ordinal, long dataIndex, char[] buffer, int bufferIndex, int length)
188                 {
189                         throw new NotImplementedException ();
190                 }
191
192                 [MonoTODO]
193                 public OdbcDataReader GetData (int ordinal)
194                 {
195                         throw new NotImplementedException ();
196                 }
197
198                 public string GetDataTypeName (int index)
199                 {
200                         return GetColumn(index).OdbcType.ToString();
201                 }
202
203                 public DateTime GetDateTime (int ordinal)
204                 {
205                         return (DateTime) GetValue(ordinal);
206                 }
207
208                 [MonoTODO]
209                 public decimal GetDecimal (int ordinal)
210                 {
211                         throw new NotImplementedException ();
212                 }
213
214                 public double GetDouble (int ordinal)
215                 {
216                         return (double) GetValue(ordinal);
217                 }
218
219                 public Type GetFieldType (int index)
220                 {
221                         return GetColumn(index).DataType;
222                 }
223
224                 public float GetFloat (int ordinal)
225                 {
226                         return (float) GetValue(ordinal);
227                 }
228
229                 [MonoTODO]
230                 public Guid GetGuid (int ordinal)
231                 {
232                         throw new NotImplementedException ();
233                 }
234
235                 public short GetInt16 (int ordinal)
236                 {
237                         return (short) GetValue(ordinal);
238                 }
239
240                 public int GetInt32 (int ordinal)
241                 {
242                         return (int) GetValue(ordinal);
243                 }
244
245                 public long GetInt64 (int ordinal)
246                 {
247                         return (long) GetValue(ordinal);
248                 }
249
250                 public string GetName (int index)
251                 {
252                         if (currentRow == -1)
253                                 return null;
254                         return GetColumn(index).ColumnName;
255                 }
256
257                 public int GetOrdinal (string name)
258                 {
259                         if (currentRow == -1)
260                                 throw new IndexOutOfRangeException ();
261
262                         int i=ColIndex(name);
263
264                         if (i==-1)
265                                 throw new IndexOutOfRangeException ();
266                         else
267                                 return i;
268                 }
269
270                 [MonoTODO]
271                 public DataTable GetSchemaTable() 
272                 {       
273
274                         DataTable dataTableSchema = null;
275                         // Only Results from SQL SELECT Queries 
276                         // get a DataTable for schema of the result
277                         // otherwise, DataTable is null reference
278                         if(cols.Length > 0) 
279                         {
280                                 
281                                 dataTableSchema = new DataTable ();
282                                 
283                                 dataTableSchema.Columns.Add ("ColumnName", typeof (string));
284                                 dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int));
285                                 dataTableSchema.Columns.Add ("ColumnSize", typeof (int));
286                                 dataTableSchema.Columns.Add ("NumericPrecision", typeof (int));
287                                 dataTableSchema.Columns.Add ("NumericScale", typeof (int));
288                                 dataTableSchema.Columns.Add ("IsUnique", typeof (bool));
289                                 dataTableSchema.Columns.Add ("IsKey", typeof (bool));
290                                 DataColumn dc = dataTableSchema.Columns["IsKey"];
291                                 dc.AllowDBNull = true; // IsKey can have a DBNull
292                                 dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string));
293                                 dataTableSchema.Columns.Add ("BaseColumnName", typeof (string));
294                                 dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string));
295                                 dataTableSchema.Columns.Add ("BaseTableName", typeof (string));
296                                 dataTableSchema.Columns.Add ("DataType", typeof(Type));
297                                 dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool));
298                                 dataTableSchema.Columns.Add ("ProviderType", typeof (int));
299                                 dataTableSchema.Columns.Add ("IsAliased", typeof (bool));
300                                 dataTableSchema.Columns.Add ("IsExpression", typeof (bool));
301                                 dataTableSchema.Columns.Add ("IsIdentity", typeof (bool));
302                                 dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool));
303                                 dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool));
304                                 dataTableSchema.Columns.Add ("IsHidden", typeof (bool));
305                                 dataTableSchema.Columns.Add ("IsLong", typeof (bool));
306                                 dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool));
307
308                                 DataRow schemaRow;
309                                                                 
310                                 for (int i = 0; i < cols.Length; i += 1 ) 
311                                 {
312                                         OdbcColumn col=GetColumn(i);
313                                         //Console.WriteLine("{0}:{1}:{2}",col.ColumnName,col.DataType,col.OdbcType);
314
315                                         schemaRow = dataTableSchema.NewRow ();
316                                         dataTableSchema.Rows.Add (schemaRow);
317                                                                                 
318                                         schemaRow["ColumnName"] = col.ColumnName;
319                                         schemaRow["ColumnOrdinal"] = i + 1;
320                                         
321                                         schemaRow["ColumnSize"] = col.MaxLength;
322                                         schemaRow["NumericPrecision"] = 0;
323                                         schemaRow["NumericScale"] = 0;
324                                         // TODO: need to get KeyInfo
325                                         schemaRow["IsUnique"] = false;
326                                         schemaRow["IsKey"] = DBNull.Value;                                      
327
328                                         schemaRow["BaseCatalogName"] = "";                              
329                                         schemaRow["BaseColumnName"] = col.ColumnName;
330                                         schemaRow["BaseSchemaName"] = "";
331                                         schemaRow["BaseTableName"] = "";
332                                         schemaRow["DataType"] = col.DataType;
333
334                                         schemaRow["AllowDBNull"] = col.AllowDBNull;
335                                         
336                                         schemaRow["ProviderType"] = (int) col.OdbcType;
337                                         // TODO: all of these
338                                         schemaRow["IsAliased"] = false;
339                                         schemaRow["IsExpression"] = false;
340                                         schemaRow["IsIdentity"] = false;
341                                         schemaRow["IsAutoIncrement"] = false;
342                                         schemaRow["IsRowVersion"] = false;
343                                         schemaRow["IsHidden"] = false;
344                                         schemaRow["IsLong"] = false;
345                                         schemaRow["IsReadOnly"] = false;
346                                         
347         //                              schemaRow.AcceptChanges();
348                                         
349                                 }
350
351                         }
352                         dataTableSchema.AcceptChanges();
353                         return dataTableSchema;
354                 }
355
356                 public string GetString (int ordinal)
357                 {
358                         return (string) GetValue(ordinal);
359                 }
360
361                 [MonoTODO]
362                 public TimeSpan GetTimeSpan (int ordinal)
363                 {
364                         throw new NotImplementedException ();
365                 }
366
367                 public object GetValue (int ordinal)
368                 {
369                         if (currentRow == -1)
370                                 throw new IndexOutOfRangeException ();
371
372                         if (ordinal>cols.Length-1 || ordinal<0)
373                                 throw new IndexOutOfRangeException ();
374
375                         OdbcReturn ret;
376                         int outsize=0, bufsize;
377                         byte[] buffer;
378                         OdbcColumn col=GetColumn(ordinal);
379                         object DataValue=null;
380                         ushort ColIndex=Convert.ToUInt16(ordinal+1);
381
382                         // Check cached values
383                         if (col.Value==null)
384                         {
385
386                                 // odbc help file
387                                 // mk:@MSITStore:C:\program%20files\Microsoft%20Data%20Access%20SDK\Docs\odbc.chm::/htm/odbcc_data_types.htm
388                                 switch (col.OdbcType)
389                                 {
390                                         case OdbcType.Decimal:
391                                                 bufsize=50;
392                                                 buffer=new byte[bufsize];  // According to sqlext.h, use SQL_CHAR for decimal
393                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.Char, buffer, bufsize, ref outsize);
394                                                 if (outsize!=-1)
395                                                         DataValue=Decimal.Parse(System.Text.Encoding.Default.GetString(buffer));
396                                                 break;
397                                         case OdbcType.TinyInt:
398                                                 short short_data=0;
399                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.TinyInt, ref short_data, 0, ref outsize);
400                                                 DataValue=short_data;
401                                                 break;
402                                         case OdbcType.Int:
403                                                 int int_data=0;
404                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.Int, ref int_data, 0, ref outsize);
405                                                 DataValue=int_data;
406                                                 break;
407                                         case OdbcType.BigInt:
408                                                 long long_data=0;
409                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.BigInt, ref long_data, 0, ref outsize);
410                                                 DataValue=long_data;
411                                                 break;
412                                         case OdbcType.NVarChar:
413                                                 bufsize=col.MaxLength*2+1; // Unicode is double byte
414                                                 buffer=new byte[bufsize];
415                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.NVarChar, buffer, bufsize, ref outsize);
416                                                 if (outsize!=-1)
417                                                         DataValue=System.Text.Encoding.Unicode.GetString(buffer,0,outsize);
418                                                 break;
419                                         case OdbcType.VarChar:
420                                                 bufsize=col.MaxLength+1;
421                                                 buffer=new byte[bufsize];  // According to sqlext.h, use SQL_CHAR for both char and varchar
422                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.Char, buffer, bufsize, ref outsize);
423                                                 if (outsize!=-1)
424                                                         DataValue=System.Text.Encoding.Default.GetString(buffer,0,outsize);
425                                                 break;
426                                         case OdbcType.Real:
427                                                 float float_data=0;
428                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.Real, ref float_data, 0, ref outsize);
429                                                 DataValue=float_data;
430                                                 break;
431                                         case OdbcType.Timestamp:
432                                         case OdbcType.DateTime:
433                                                 OdbcTimestamp ts_data=new OdbcTimestamp();
434                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.DateTime, ref ts_data, 0, ref outsize);
435                                                 if (outsize!=-1) // This means SQL_NULL_DATA 
436                                                         DataValue=new DateTime(ts_data.year,ts_data.month,ts_data.day,ts_data.hour,
437                                                                 ts_data.minute,ts_data.second,Convert.ToInt32(ts_data.fraction));
438                                                 break;
439                                         default:
440                                                 //Console.WriteLine("Fetching unsupported data type as string: "+col.OdbcType.ToString());
441                                                 bufsize=255;
442                                                 buffer=new byte[bufsize];
443                                                 ret=libodbc.SQLGetData(hstmt, ColIndex, OdbcType.Char, buffer, bufsize, ref outsize);
444                                                 DataValue=System.Text.Encoding.Default.GetString(buffer);
445                                                 break;
446                                 }
447
448                                 if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) 
449                                         throw new OdbcException(new OdbcError("SQLGetData",OdbcHandleType.Stmt,hstmt));
450
451                                 if (outsize==-1) // This means SQL_NULL_DATA 
452                                         col.Value=DBNull.Value;
453                                 else
454                                         col.Value=DataValue;
455                         }
456                         return col.Value;
457                 }
458
459                 [MonoTODO]
460                 public int GetValues (object[] values)
461                 {
462                         throw new NotImplementedException ();
463                 }
464
465                 [MonoTODO]
466                 IDataReader IDataRecord.GetData (int ordinal)
467                 {
468                         throw new NotImplementedException ();
469                 }
470
471                 [MonoTODO]
472                 void IDisposable.Dispose ()
473                 {
474                 }
475
476                 [MonoTODO]
477                 IEnumerator IEnumerable.GetEnumerator ()
478                 {
479                         throw new NotImplementedException ();
480                 }
481
482                 public bool IsDBNull (int ordinal)
483                 {
484                         return (GetValue(ordinal) is DBNull);
485                 }
486
487                 public bool NextResult ()
488                 {
489                         OdbcReturn ret=libodbc.SQLFetch(hstmt);
490                         if (ret!=OdbcReturn.Success)
491                                 currentRow=-1;
492                         else
493                                 currentRow++;
494                         // Clear cached values from last record
495                         foreach (OdbcColumn col in cols)
496                         {
497                                 if (col!=null)
498                                         col.Value=null;
499                         }
500                         return (ret==OdbcReturn.Success);
501                 }
502
503                 public bool Read ()
504                 {
505                         return NextResult();
506                 }
507
508                 #endregion
509         }
510 }