3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 /// DB2DriverCS - A DB2 driver for .Net
\r
24 /// Copyright 2003 By Christopher Bockner
\r
25 /// Released under the terms of the MIT/X11 Licence
\r
26 /// Please refer to the Licence.txt file that should be distributed with this package
\r
27 /// This software requires that DB2 client software be installed correctly on the machine
\r
28 /// (or instance) on which the driver is running.
\r
32 using System.Runtime.InteropServices;
\r
33 using System.Globalization;
\r
36 namespace DB2ClientCS
\r
39 /// Summary description for DB2ClientDataReader.
\r
40 /// DB2ClientDataReader.
\r
42 public class DB2ClientDataReader : IDataReader
\r
44 internal DataTable rs; //Our result set is a datatable
\r
45 internal DB2ClientConnection db2Conn; //The connection we're working with
\r
46 internal IntPtr hwndStmt; //The statement handle returning the results
\r
47 private int row=-1; //Row pointer
\r
48 private int numCols=0;
\r
50 #region Constructors and destructors
\r
54 /// <param name="con"></Connection object to DB2>
\r
55 /// <param name="com"></Command object>
\r
56 internal DB2ClientDataReader(DB2ClientConnection con, DB2ClientCommand com)
\r
59 hwndStmt = com.statementHandle; //We have access to the results through the statement handle
\r
63 DB2ClientUtils util = new DB2ClientUtils();
\r
64 rs = new DataTable();
\r
66 sqlRet = DB2ClientPrototypes.SQLNumResultCols(hwndStmt, ref numCols);
\r
67 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLNumResultCols");
\r
69 byte[][] dbVals = new byte[(int)numCols][];
\r
70 IntPtr[] sqlLen_or_IndPtr = new IntPtr[numCols];
\r
72 PrepareResults(dbVals, sqlLen_or_IndPtr);
\r
73 FetchResults(dbVals, sqlLen_or_IndPtr, rs);
\r
77 /// Constructor for use with prepared statements
\r
80 internal DB2ClientDataReader(DB2ClientConnection con, DB2ClientCommand com, bool prepared)
\r
83 hwndStmt = com.statementHandle; //We have access to the results through the statement handle
\r
87 DB2ClientUtils util = new DB2ClientUtils();
\r
88 rs = new DataTable();
\r
90 sqlRet = DB2ClientPrototypes.SQLNumResultCols(hwndStmt, ref numCols);
\r
91 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLNumResultCols");
\r
93 byte[][] dbVals = new byte[(int)numCols][];
\r
94 IntPtr[] sqlLen_or_IndPtr = new IntPtr[numCols];
\r
97 PrepareResults(dbVals, sqlLen_or_IndPtr);
\r
98 FetchResults(dbVals, sqlLen_or_IndPtr, rs);
\r
102 public void Dispose()
\r
108 #region Depth property
\r
110 ///Depth of nesting for the current row, need to figure out what this translates into
\r
113 private int depth = 0;
\r
122 #region IsClosed property
\r
124 /// True if the reader is closed.
\r
126 private bool isClosed = true;
\r
127 public bool IsClosed
\r
135 #region RecordsAffected property
\r
137 /// Number of records affected by this operation. Will be zero until we close the
\r
140 private int recordsAffected = 0;
\r
141 public int RecordsAffected
\r
145 return recordsAffected;
\r
151 #region Close method
\r
154 public void Close()
\r
158 recordsAffected = rs.Rows.Count;
\r
165 #region GetSchemaTable
\r
167 /// We'll return an empty table for now...ughh this one will be tedious to write
\r
169 public DataTable GetSchemaTable()
\r
171 throw new DB2ClientException ("TBD");
\r
174 #region NextResult
\r
176 /// Ummm is this related to SQLBulkOperations stuff..?
\r
178 public bool NextResult()
\r
180 throw new DB2ClientException("To be done");
\r
182 //Deferring the meat of this until the batch stuff is implemented
\r
187 /// Apparently, this function does nothing other than tell you if you can move to the
\r
188 /// next row in the resultset. I have to move the fetching stuff elswhere...
\r
192 if (isClosed) return false;
\r
194 //do something with the fetched data now...
\r
195 if(row < rs.Rows.Count)
\r
203 #region Describe/Bind/Fetch functions
\r
205 ///Broke these out so that we can use different paths for Immediate executions and Prepared executions
\r
207 /// Does the describe and bind steps for the query result set. Called for both immediate and prepared queries.
\r
209 private void PrepareResults(byte[][] dbVals, IntPtr[] sqlLen_or_IndPtr)
\r
212 StringBuilder colName = new StringBuilder(18);
\r
213 short colNameMaxLength=18;
\r
214 IntPtr colNameLength=IntPtr.Zero;
\r
215 IntPtr sqlDataType=IntPtr.Zero;
\r
216 IntPtr colSize=IntPtr.Zero;
\r
217 IntPtr scale=IntPtr.Zero;
\r
218 IntPtr nullable=IntPtr.Zero;
\r
219 DB2ClientUtils util = new DB2ClientUtils();
\r
220 for (ushort i=1; i<=numCols; i++)
\r
222 sqlRet = DB2ClientPrototypes.SQLDescribeCol(hwndStmt, i, colName, colNameMaxLength, colNameLength, ref sqlDataType, ref colSize, ref scale, ref nullable);
\r
223 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLDescribeCol");
\r
224 ///At this point I have the data type information as well, but for now I will insert the data as
\r
225 ///Ansi strings and see how it goes. Maybe we can speed things up later...
\r
227 rs.Columns.Add(colName.ToString());
\r
229 sqlLen_or_IndPtr[i-1] = new IntPtr();
\r
230 dbVals[i-1] = new byte[(int)colSize];
\r
235 switch ((int)sqlDataType)
\r
237 case DB2ClientConstants.SQL_DECIMAL: //These types are treated as SQL_C_CHAR for binding purposes
\r
238 case DB2ClientConstants.SQL_TYPE_DATE:
\r
239 case DB2ClientConstants.SQL_TYPE_TIME:
\r
240 case DB2ClientConstants.SQL_TYPE_TIMESTAMP:
\r
241 case DB2ClientConstants.SQL_VARCHAR:
\r
242 sqlRet = DB2ClientPrototypes.SQLBindCol(hwndStmt, i, DB2ClientConstants.SQL_C_CHAR, dbVals[i-1],(short)colSize+1, (int)sqlLen_or_IndPtr[i-1]);
\r
245 sqlRet = DB2ClientPrototypes.SQLBindCol(hwndStmt, i, (short)sqlDataType, dbVals[i-1],(short)colSize+1, (int)sqlLen_or_IndPtr[i-1]);
\r
248 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLBindCol");
\r
250 catch(DB2ClientException e)
\r
252 System.Console.Write(e.Message);
\r
258 /// FetchResults does what it says.
\r
260 /// <param name="dbVals"></param>
\r
261 /// <param name="sqlLen_or_IndPtr"></param>
\r
262 /// <param name="rs"></param>
\r
263 private void FetchResults(byte[][] dbVals, IntPtr[] sqlLen_or_IndPtr, DataTable rs)
\r
266 DB2ClientUtils util = new DB2ClientUtils();
\r
268 sqlRet = DB2ClientPrototypes.SQLFetch(hwndStmt);
\r
269 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLFetch 1");
\r
271 while(sqlRet != DB2ClientConstants.SQL_NO_DATA_FOUND)
\r
273 DataRow newRow = rs.NewRow();
\r
274 for (short y=1;y<=numCols;y++)
\r
275 newRow[y-1] = System.Text.Encoding.Default.GetString(dbVals[y-1]);
\r
277 rs.Rows.Add(newRow);
\r
278 sqlRet = DB2ClientPrototypes.SQLFetch(hwndStmt);
\r
279 util.DB2CheckReturn(sqlRet, DB2ClientConstants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLFetch 2");
\r
284 #region IDataRecord Interface
\r
285 ///Code for the IDataRecord interface
\r
290 private int fieldCount = -1;
\r
291 public int FieldCount
\r
298 fieldCount = rs.Columns.Count;
\r
303 #region Item accessors
\r
304 public object this[string name]
\r
308 return rs.Rows[row][name];
\r
311 public object this[int col]
\r
315 return rs.Rows[row][col];
\r
319 #region GetBoolean method
\r
321 ///Use the Convert class for all of these returns
\r
323 public bool GetBoolean(int col)
\r
325 return Convert.ToBoolean(this[col]);
\r
332 public byte GetByte(int col)
\r
334 return Convert.ToByte(this[col]);
\r
339 /// GetBytes, return a stream of bytes
\r
341 public long GetBytes(int col, long fieldOffset, byte[] buffer, int bufferOffset, int length)
\r
343 //Hmm... How shall we deal with this one?
\r
349 ///GetChar, return column as a char
\r
351 public char GetChar(int col)
\r
353 return Convert.ToChar(this[col]);
\r
358 ///GetChars, returns char array
\r
360 public long GetChars(int col, long fieldOffset, char[] buffer, int bufferOffset, int length)
\r
362 //Again, not sure how I'll deal with this just yet
\r
370 public IDataReader GetData(int col)
\r
372 //Have to research this one, not quite sure what the docs mean
\r
373 //DB2 does have some structured data types, is that what this is for?
\r
374 throw new DB2ClientException("Not yet supported.");
\r
377 #region GetDataTypeName
\r
379 ///GetDataTypeName return the type of data
\r
381 public string GetDataTypeName(int col)
\r
383 //I could check the meta data as a starting point for this one, but until I implement
\r
384 //returning the result sets, I'm not exactly sure what info I'll have, so this function
\r
385 //waits until then...
\r
386 throw new DB2ClientException("Not yet implemented");
\r
389 #region GetDateTime
\r
391 /// GetDateTime method
\r
393 public string NewGetDateTime(int col)
\r
395 return Convert.ToString(this[col]);
\r
397 public DateTime GetDateTime(int col)
\r
399 return Convert.ToDateTime(this[col]);
\r
404 ///GetDecimal method
\r
406 public decimal GetDecimal(int col)
\r
408 return Convert.ToDecimal(this[col]);
\r
415 public double GetDouble(int col)
\r
417 return Convert.ToDouble(this[col]);
\r
420 #region GetFieldType
\r
422 /// Type GetFieldType
\r
424 public Type GetFieldType(int col)
\r
426 //Again need more research here
\r
427 return typeof(int);
\r
434 public float GetFloat(int col)
\r
436 return (float) Convert.ToDouble(this[col].ToString(),new CultureInfo("en-US").NumberFormat);
\r
443 public Guid GetGuid(int col)
\r
445 // a Guid is a 128 bit unique value. Could be like a GENERATE UNIQUE in DB2
\r
446 // as usual, need more research
\r
447 throw new DB2ClientException("TBD");
\r
450 #region The GetInt?? series
\r
454 public short GetInt16(int col)
\r
456 return Convert.ToInt16(this[col]);
\r
461 public int GetInt32(int col)
\r
463 return Convert.ToInt32(this[col]);
\r
468 public long GetInt64(int col)
\r
470 return Convert.ToInt64(this[col]);
\r
475 ///GetName, returns the name of the field
\r
477 public string GetName(int col)
\r
479 return (rs.Columns[col].ColumnName);
\r
484 /// GetOrdinal, return the index of the named column
\r
486 public int GetOrdinal(string name)
\r
488 return rs.Columns[name].Ordinal;
\r
493 /// GetString returns a string
\r
495 public string GetString(int col)
\r
497 return Convert.ToString(this[col]);
\r
501 #region GetLobLocator
\r
503 ///Returns a LOB Locator class
\r
505 //DB2ClientLOBLocator GetLobLocator(int col)
\r
513 /// GetVCalue, returns an object
\r
515 public object GetValue(int col)
\r
522 /// GetValues returns all columns in the row through the argument, and the number of columns in the return value
\r
524 public int GetValues(object[] values)
\r
526 int numCols = FieldCount;
\r
527 if (values.Length<numCols)
\r
528 throw new DB2ClientException("GetValues argument too small for number of columns in row.");
\r
529 for (int i = 0; i<=numCols; i++)
\r
530 values[i] = this[i];
\r
536 /// IsDBNull Is the column null
\r
538 public bool IsDBNull(int col)
\r
540 //Proper implementation once I get the SQLDescribe/SQLBind/SQLFetch stuff in place
\r
545 #endregion ///For IDataRecord
\r