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