// // System.Data.Odbc.OdbcDataReader // // Author: // Brian Ritchie (brianlritchie@hotmail.com) // Daniel Morgan // Sureshkumar T (2004) // // Copyright (C) Brian Ritchie, 2002 // Copyright (C) Daniel Morgan, 2002 // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Collections; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Globalization; using System.Text; namespace System.Data.Odbc { #if NET_2_0 public sealed class OdbcDataReader : DbDataReader #else public sealed class OdbcDataReader : MarshalByRefObject, IDataReader, IDisposable, IDataRecord, IEnumerable #endif { #region Fields private OdbcCommand command; private bool open; private int currentRow; private OdbcColumn[] cols; private IntPtr hstmt; private int _recordsAffected = -1; bool disposed; private DataTable _dataTableSchema; private CommandBehavior behavior; #endregion #region Constructors internal OdbcDataReader (OdbcCommand command, CommandBehavior behavior) { this.command = command; this.CommandBehavior = behavior; open = true; currentRow = -1; hstmt = command.hStmt; // Init columns array; short colcount = 0; libodbc.SQLNumResultCols (hstmt, ref colcount); cols = new OdbcColumn [colcount]; GetColumns (); } internal OdbcDataReader (OdbcCommand command, CommandBehavior behavior, int recordAffected) : this (command, behavior) { _recordsAffected = recordAffected; } #endregion #region Properties private CommandBehavior CommandBehavior { get { return behavior; } set { behavior = value; } } public #if NET_2_0 override #endif // NET_2_0 int Depth { get { return 0; // no nested selects supported } } public #if NET_2_0 override #endif // NET_2_0 int FieldCount { get { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); return cols.Length; } } public #if NET_2_0 override #endif // NET_2_0 bool IsClosed { get { return !open; } } public #if NET_2_0 override #endif // NET_2_0 object this [string value] { get { int pos = GetOrdinal (value); return this [pos]; } } public #if NET_2_0 override #endif // NET_2_0 object this [int i] { get { return GetValue (i); } } public #if NET_2_0 override #endif // NET_2_0 int RecordsAffected { get { return _recordsAffected; } } [MonoTODO] public #if NET_2_0 override #endif // NET_2_0 bool HasRows { get { throw new NotImplementedException(); } } private OdbcConnection Connection { get { if (command != null) return command.Connection; return null; } } #endregion #region Methods private int ColIndex (string colname) { int i = 0; foreach (OdbcColumn col in cols) { if (col != null) { if (col.ColumnName == colname) return i; if (String.Compare (col.ColumnName, colname, true) == 0) return i; } i++; } return -1; } // Dynamically load column descriptions as needed. private OdbcColumn GetColumn (int ordinal) { if (cols [ordinal] == null) { short bufsize = 255; byte [] colname_buffer = new byte [bufsize]; string colname; short colname_size = 0; uint ColSize = 0; short DecDigits = 0, Nullable = 0, dt = 0; OdbcReturn ret = libodbc.SQLDescribeCol (hstmt, Convert.ToUInt16 (ordinal + 1), colname_buffer, bufsize, ref colname_size, ref dt, ref ColSize, ref DecDigits, ref Nullable); if ((ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, hstmt); colname = RemoveTrailingNullChar (Encoding.Unicode.GetString (colname_buffer)); OdbcColumn c = new OdbcColumn (colname, (SQL_TYPE) dt); c.AllowDBNull = (Nullable != 0); c.Digits = DecDigits; if (c.IsVariableSizeType) c.MaxLength = (int) ColSize; cols [ordinal] = c; } return cols [ordinal]; } // Load all column descriptions private void GetColumns () { for(int i = 0; i < cols.Length; i++) GetColumn (i); } public #if NET_2_0 override #endif // NET_2_0 void Close () { // FIXME : have to implement output parameter binding open = false; currentRow = -1; this.command.FreeIfNotPrepared (); if ((this.CommandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection) this.command.Connection.Close (); } #if ONLY_1_1 ~OdbcDataReader () { this.Dispose (false); } #endif public #if NET_2_0 override #endif // NET_2_0 bool GetBoolean (int i) { return (bool) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 byte GetByte (int i) { return Convert.ToByte (GetValue (i)); } public #if NET_2_0 override #endif // NET_2_0 long GetBytes (int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { if (IsClosed) throw new InvalidOperationException ("Reader is not open."); if (currentRow == -1) throw new InvalidOperationException ("No data available."); OdbcReturn ret = OdbcReturn.Error; bool copyBuffer = false; int returnVal = 0, outsize = 0; byte [] tbuff = new byte [length+1]; if (buffer == null) length = 0; ret=libodbc.SQLGetData (hstmt, (ushort) (i + 1), SQL_C_TYPE.BINARY, tbuff, length, ref outsize); if (ret == OdbcReturn.NoData) return 0; if ( (ret != OdbcReturn.Success) && (ret != OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, hstmt); OdbcException odbcException = null; if ( (ret == OdbcReturn.SuccessWithInfo)) odbcException = Connection.CreateOdbcException ( OdbcHandleType.Stmt, hstmt); if (buffer == null) return outsize; //if buffer is null,return length of the field if (ret == OdbcReturn.SuccessWithInfo) { if (outsize == (int) OdbcLengthIndicator.NoTotal) copyBuffer = true; else if (outsize == (int) OdbcLengthIndicator.NullData) { copyBuffer = false; returnVal = -1; } else { string sqlstate = odbcException.Errors [0].SQLState; //SQLState: String Data, Right truncated if (sqlstate != libodbc.SQLSTATE_RIGHT_TRUNC) throw odbcException; copyBuffer = true; } } else { copyBuffer = outsize == -1 ? false : true; returnVal = outsize; } if (copyBuffer) { if (outsize == (int) OdbcLengthIndicator.NoTotal) { int j = 0; while (tbuff [j] != libodbc.C_NULL) { buffer [bufferIndex + j] = tbuff [j]; j++; } returnVal = j; } else { int read_bytes = Math.Min (outsize, length); for (int j = 0; j < read_bytes; j++) buffer [bufferIndex + j] = tbuff [j]; returnVal = read_bytes; } } return returnVal; } [MonoTODO] public #if NET_2_0 override #endif // NET_2_0 char GetChar (int i) { throw new NotImplementedException (); } [MonoTODO] public #if NET_2_0 override #endif // NET_2_0 long GetChars (int i, long dataIndex, char[] buffer, int bufferIndex, int length) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); if (currentRow == -1) throw new InvalidOperationException ("No data available."); if (i < 0 || i >= FieldCount) throw new IndexOutOfRangeException (); throw new NotImplementedException (); } [MonoTODO] [EditorBrowsableAttribute (EditorBrowsableState.Never)] #if ONLY_1_1 public #endif #if NET_2_0 new #endif IDataReader GetData (int i) { throw new NotImplementedException (); } public #if NET_2_0 override #endif // NET_2_0 string GetDataTypeName (int i) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); if (i < 0 || i >= FieldCount) throw new IndexOutOfRangeException (); return GetColumnAttributeStr (i + 1, FieldIdentifier.TypeName); } public DateTime GetDate (int i) { return GetDateTime (i); } public #if NET_2_0 override #endif // NET_2_0 DateTime GetDateTime (int i) { return (DateTime) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 decimal GetDecimal (int i) { return (decimal) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 double GetDouble (int i) { return (double) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 Type GetFieldType (int i) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); return GetColumn (i).DataType; } public #if NET_2_0 override #endif // NET_2_0 float GetFloat (int i) { return (float) GetValue (i); } [MonoTODO] public #if NET_2_0 override #endif // NET_2_0 Guid GetGuid (int i) { throw new NotImplementedException (); } public #if NET_2_0 override #endif // NET_2_0 short GetInt16 (int i) { return (short) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 int GetInt32 (int i) { return (int) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 long GetInt64 (int i) { return (long) GetValue (i); } public #if NET_2_0 override #endif // NET_2_0 string GetName (int i) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); return GetColumn (i).ColumnName; } public #if NET_2_0 override #endif // NET_2_0 int GetOrdinal (string value) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); if (value == null) throw new ArgumentNullException ("fieldName"); int i = ColIndex (value); if (i == -1) throw new IndexOutOfRangeException (); return i; } [MonoTODO] public #if NET_2_0 override #endif // NET_2_0 DataTable GetSchemaTable () { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); // FIXME : // * Map OdbcType to System.Type and assign to DataType. // This will eliminate the need for IsStringType in // OdbcColumn if (_dataTableSchema != null) return _dataTableSchema; DataTable dataTableSchema = null; // Only Results from SQL SELECT Queries // get a DataTable for schema of the result // otherwise, DataTable is null reference if (cols.Length > 0) { dataTableSchema = new DataTable (); dataTableSchema.Columns.Add ("ColumnName", typeof (string)); dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int)); dataTableSchema.Columns.Add ("ColumnSize", typeof (int)); dataTableSchema.Columns.Add ("NumericPrecision", typeof (int)); dataTableSchema.Columns.Add ("NumericScale", typeof (int)); dataTableSchema.Columns.Add ("IsUnique", typeof (bool)); dataTableSchema.Columns.Add ("IsKey", typeof (bool)); DataColumn dc = dataTableSchema.Columns["IsKey"]; dc.AllowDBNull = true; // IsKey can have a DBNull dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string)); dataTableSchema.Columns.Add ("BaseColumnName", typeof (string)); dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string)); dataTableSchema.Columns.Add ("BaseTableName", typeof (string)); dataTableSchema.Columns.Add ("DataType", typeof(Type)); dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool)); dataTableSchema.Columns.Add ("ProviderType", typeof (int)); dataTableSchema.Columns.Add ("IsAliased", typeof (bool)); dataTableSchema.Columns.Add ("IsExpression", typeof (bool)); dataTableSchema.Columns.Add ("IsIdentity", typeof (bool)); dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool)); dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool)); dataTableSchema.Columns.Add ("IsHidden", typeof (bool)); dataTableSchema.Columns.Add ("IsLong", typeof (bool)); dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool)); DataRow schemaRow; for (int i = 0; i < cols.Length; i += 1 ) { OdbcColumn col=GetColumn(i); schemaRow = dataTableSchema.NewRow (); dataTableSchema.Rows.Add (schemaRow); schemaRow ["ColumnName"] = col.ColumnName; schemaRow ["ColumnOrdinal"] = i; schemaRow ["ColumnSize"] = col.MaxLength; schemaRow ["NumericPrecision"] = GetColumnAttribute (i+1, FieldIdentifier.Precision); schemaRow ["NumericScale"] = GetColumnAttribute (i+1, FieldIdentifier.Scale); schemaRow ["BaseTableName"] = GetColumnAttributeStr (i+1, FieldIdentifier.TableName); schemaRow ["BaseSchemaName"] = GetColumnAttributeStr (i+1, FieldIdentifier.SchemaName); schemaRow ["BaseCatalogName"] = GetColumnAttributeStr (i+1, FieldIdentifier.CatelogName); schemaRow ["BaseColumnName"] = GetColumnAttributeStr (i+1, FieldIdentifier.BaseColumnName); schemaRow ["DataType"] = col.DataType; schemaRow ["IsUnique"] = false; schemaRow ["IsKey"] = DBNull.Value; schemaRow ["AllowDBNull"] = GetColumnAttribute (i+1, FieldIdentifier.Nullable) != libodbc.SQL_NO_NULLS; schemaRow ["ProviderType"] = (int) col.OdbcType; schemaRow ["IsAutoIncrement"] = GetColumnAttribute (i+1, FieldIdentifier.AutoUniqueValue) == libodbc.SQL_TRUE; schemaRow ["IsExpression"] = schemaRow.IsNull ("BaseTableName") || (string) schemaRow ["BaseTableName"] == String.Empty; schemaRow ["IsAliased"] = (string) schemaRow ["BaseColumnName"] != (string) schemaRow ["ColumnName"]; schemaRow ["IsReadOnly"] = ((bool) schemaRow ["IsExpression"] || GetColumnAttribute (i+1, FieldIdentifier.Updatable) == libodbc.SQL_ATTR_READONLY); // FIXME: all of these schemaRow ["IsIdentity"] = false; schemaRow ["IsRowVersion"] = false; schemaRow ["IsHidden"] = false; schemaRow ["IsLong"] = false; // FIXME: according to Brian, // this does not work on MS .NET // however, we need it for Mono // for now // schemaRow.AcceptChanges(); } // set primary keys DataRow [] rows = dataTableSchema.Select ("BaseTableName <> ''", "BaseCatalogName, BaseSchemaName, BaseTableName ASC"); string lastTableName = String.Empty, lastSchemaName = String.Empty, lastCatalogName = String.Empty; string [] keys = null; // assumed to be sorted. foreach (DataRow row in rows) { string tableName = (string) row ["BaseTableName"]; string schemaName = (string) row ["BaseSchemaName"]; string catalogName = (string) row ["BaseCatalogName"]; if (tableName != lastTableName || schemaName != lastSchemaName || catalogName != lastCatalogName) keys = GetPrimaryKeys (catalogName, schemaName, tableName); if (keys != null && Array.BinarySearch (keys, (string) row ["BaseColumnName"]) >= 0) { row ["IsKey"] = true; row ["IsUnique"] = true; row ["AllowDBNull"] = false; GetColumn ( ColIndex ( (string) row ["ColumnName"])).AllowDBNull = false; } lastTableName = tableName; lastSchemaName = schemaName; lastCatalogName = catalogName; } dataTableSchema.AcceptChanges (); } return (_dataTableSchema = dataTableSchema); } public #if NET_2_0 override #endif // NET_2_0 string GetString (int i) { object ret = GetValue (i); if (ret != null && ret.GetType () != typeof (string)) return Convert.ToString (ret); else return (string) GetValue (i); } [MonoTODO] public TimeSpan GetTime (int i) { throw new NotImplementedException (); } public #if NET_2_0 override #endif // NET_2_0 object GetValue (int i) { if (IsClosed) throw new InvalidOperationException ("The reader is closed."); if (currentRow == -1) throw new InvalidOperationException ("No data available."); if (i > cols.Length-1 || i < 0) throw new IndexOutOfRangeException (); OdbcReturn ret; int outsize = 0, bufsize; byte[] buffer; OdbcColumn col = GetColumn (i); object DataValue = null; ushort ColIndex = Convert.ToUInt16 (i + 1); // Check cached values if (col.Value == null) { // odbc help file // mk:@MSITStore:C:\program%20files\Microsoft%20Data%20Access%20SDK\Docs\odbc.chm::/htm/odbcc_data_types.htm switch (col.OdbcType) { case OdbcType.Bit: short bit_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref bit_data, 0, ref outsize); if (outsize != (int) OdbcLengthIndicator.NullData) DataValue = bit_data == 0 ? "False" : "True"; break; case OdbcType.Numeric: case OdbcType.Decimal: bufsize = 50; buffer = new byte [bufsize]; // According to sqlext.h, use SQL_CHAR for decimal. // FIXME : use Numeric. ret = libodbc.SQLGetData (hstmt, ColIndex, SQL_C_TYPE.CHAR, buffer, bufsize, ref outsize); if (outsize!=-1) { byte [] temp = new byte [outsize]; for (int j = 0; j < outsize; j++) temp [j] = buffer [j]; DataValue = Decimal.Parse (Encoding.Default.GetString (temp), CultureInfo.InvariantCulture); } break; case OdbcType.TinyInt: short short_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref short_data, 0, ref outsize); DataValue = Convert.ToByte (short_data); break; case OdbcType.Int: int int_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref int_data, 0, ref outsize); DataValue = int_data; break; case OdbcType.SmallInt: short sint_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref sint_data, 0, ref outsize); DataValue = sint_data; break; case OdbcType.BigInt: long long_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref long_data, 0, ref outsize); DataValue = long_data; break; case OdbcType.NChar: bufsize = 255; buffer = new byte [bufsize]; ret = libodbc.SQLGetData (hstmt, ColIndex, SQL_C_TYPE.WCHAR, buffer, bufsize, ref outsize); if (outsize != (int) OdbcLengthIndicator.NullData) if (!(ret == OdbcReturn.SuccessWithInfo && outsize == (int) OdbcLengthIndicator.NoTotal)) DataValue = Encoding.Unicode.GetString (buffer, 0, outsize); break; case OdbcType.NText: case OdbcType.NVarChar: bufsize = (col.MaxLength < 127 ? (col.MaxLength*2+1) : 255); buffer = new byte[bufsize]; // According to sqlext.h, use SQL_CHAR for both char and varchar StringBuilder sb = new StringBuilder (); char[] charBuffer = new char[bufsize]; Decoder unicodeDecoder = Encoding.Unicode.GetDecoder (); do { ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, buffer, bufsize, ref outsize); if (ret == OdbcReturn.Error) break; // Fix for strance ODBC drivers (like psqlODBC) if (ret == OdbcReturn.Success && outsize==-1) ret = OdbcReturn.NoData; if (ret == OdbcReturn.Success || ret == OdbcReturn.SuccessWithInfo) { if (outsize >= bufsize || outsize == (int)OdbcLengthIndicator.NoTotal) outsize = bufsize; int charCount = unicodeDecoder.GetChars (buffer, 0, outsize, charBuffer, 0); string strValue = new String (charBuffer, 0, charCount); sb.Append (RemoveTrailingNullChar (strValue)); } } while (ret != OdbcReturn.NoData); DataValue = sb.ToString (); charBuffer = null; break; case OdbcType.Text: case OdbcType.VarChar: bufsize = (col.MaxLength < 255 ? (col.MaxLength+1) : 255); buffer = new byte[bufsize]; // According to sqlext.h, use SQL_CHAR for both char and varchar StringBuilder sb1 = new StringBuilder (); charBuffer = new char[bufsize]; Decoder defaultDecoder = Encoding.Default.GetDecoder(); do { ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, buffer, bufsize, ref outsize); if (ret == OdbcReturn.Error) break; // Fix for strance ODBC drivers (like psqlODBC) if (ret == OdbcReturn.Success && outsize==-1) ret = OdbcReturn.NoData; if (ret == OdbcReturn.Success || ret == OdbcReturn.SuccessWithInfo) { if (outsize >= bufsize || outsize == (int)OdbcLengthIndicator.NoTotal) outsize = bufsize - 1; int charCount = defaultDecoder.GetChars(buffer, 0, outsize, charBuffer, 0); sb1.Append(charBuffer, 0, charCount); } } while (ret != OdbcReturn.NoData); DataValue = sb1.ToString (); break; case OdbcType.Real: float float_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref float_data, 0, ref outsize); DataValue = float_data; break; case OdbcType.Double: double double_data = 0; ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref double_data, 0, ref outsize); DataValue = double_data; break; case OdbcType.Timestamp: case OdbcType.DateTime: case OdbcType.Date: case OdbcType.Time: OdbcTimestamp ts_data = new OdbcTimestamp(); ret = libodbc.SQLGetData (hstmt, ColIndex, col.SqlCType, ref ts_data, 0, ref outsize); if (outsize != -1) {// This means SQL_NULL_DATA if (col.OdbcType == OdbcType.Time) { // libodbc returns value in first three fields for OdbcType.Time DataValue = new System.TimeSpan (ts_data.year, ts_data.month, ts_data.day); } else { DataValue = new DateTime(ts_data.year, ts_data.month, ts_data.day, ts_data.hour, ts_data.minute, ts_data.second); if (ts_data.fraction != 0) DataValue = ((DateTime) DataValue).AddTicks ((long)ts_data.fraction / 100); } } break; case OdbcType.VarBinary : case OdbcType.Image : bufsize = (col.MaxLength < 255 && col.MaxLength > 0 ? col.MaxLength : 255); buffer= new byte [bufsize]; ArrayList al = new ArrayList (); //get the size of data to be returned. ret = libodbc.SQLGetData (hstmt, ColIndex, SQL_C_TYPE.BINARY, buffer, 0, ref outsize); if (outsize != (int) OdbcLengthIndicator.NullData) { do { ret = libodbc.SQLGetData (hstmt, ColIndex, SQL_C_TYPE.BINARY, buffer, bufsize, ref outsize); if (ret == OdbcReturn.Error) break; if (ret != OdbcReturn.NoData && outsize != -1) { if (outsize < bufsize) { byte [] tmparr = new byte [outsize]; Array.Copy (buffer, 0, tmparr, 0, outsize); al.AddRange (tmparr); } else al.AddRange (buffer); } else { break; } } while (ret != OdbcReturn.NoData); } DataValue = al.ToArray (typeof (byte)); break; case OdbcType.Binary : bufsize = col.MaxLength; buffer = new byte [bufsize]; GetBytes (i, 0, buffer, 0, bufsize); ret = OdbcReturn.Success; DataValue = buffer; break; default: bufsize = 255; buffer = new byte[bufsize]; ret = libodbc.SQLGetData (hstmt, ColIndex, SQL_C_TYPE.CHAR, buffer, bufsize, ref outsize); if (outsize != (int) OdbcLengthIndicator.NullData) if (! (ret == OdbcReturn.SuccessWithInfo && outsize == (int) OdbcLengthIndicator.NoTotal)) DataValue = Encoding.Default.GetString (buffer, 0, outsize); break; } if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo) && (ret!=OdbcReturn.NoData)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, hstmt); if (outsize == -1) // This means SQL_NULL_DATA col.Value = DBNull.Value; else col.Value = DataValue; } return col.Value; } public #if NET_2_0 override #endif // NET_2_0 int GetValues (object [] values) { int numValues = 0; if (IsClosed) throw new InvalidOperationException ("The reader is closed."); if (currentRow == -1) throw new InvalidOperationException ("No data available."); // copy values for (int i = 0; i < values.Length; i++) { if (i < FieldCount) { values [i] = GetValue (i); } else { values [i] = null; } } // get number of object instances in array if (values.Length < FieldCount) numValues = values.Length; else if (values.Length == FieldCount) numValues = FieldCount; else numValues = FieldCount; return numValues; } #if ONLY_1_1 void IDisposable.Dispose () { Dispose (true); GC.SuppressFinalize (this); } IEnumerator IEnumerable.GetEnumerator () { return new DbEnumerator (this); } #endif // ONLY_1_1 #if NET_2_0 public override IEnumerator GetEnumerator () { return new DbEnumerator (this); } #endif #if NET_2_0 protected override #endif void Dispose (bool disposing) { if (disposed) return; if (disposing) { // dispose managed resources Close (); } command = null; cols = null; _dataTableSchema = null; disposed = true; } public #if NET_2_0 override #endif // NET_2_0 bool IsDBNull (int i) { return (GetValue (i) is DBNull); } /// /// Move to the next result set. /// public #if NET_2_0 override #endif // NET_2_0 bool NextResult () { OdbcReturn ret = OdbcReturn.Success; ret = libodbc.SQLMoreResults (hstmt); if (ret == OdbcReturn.Success) { short colcount = 0; libodbc.SQLNumResultCols (hstmt, ref colcount); cols = new OdbcColumn [colcount]; _dataTableSchema = null; // force fresh creation GetColumns (); } return (ret == OdbcReturn.Success); } /// /// Load the next row in the current result set. /// private bool NextRow () { OdbcReturn ret = libodbc.SQLFetch (hstmt); if (ret != OdbcReturn.Success) currentRow = -1; else currentRow++; // Clear cached values from last record foreach (OdbcColumn col in cols) { if (col != null) col.Value = null; } return (ret == OdbcReturn.Success); } private int GetColumnAttribute (int column, FieldIdentifier fieldId) { OdbcReturn ret = OdbcReturn.Error; byte [] buffer = new byte [255]; short outsize = 0; int val = 0; ret = libodbc.SQLColAttribute (hstmt, (short)column, fieldId, buffer, (short)buffer.Length, ref outsize, ref val); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException ( OdbcHandleType.Stmt, hstmt); return val; } private string GetColumnAttributeStr (int column, FieldIdentifier fieldId) { OdbcReturn ret = OdbcReturn.Error; byte [] buffer = new byte [255]; short outsize = 0; int val = 0; ret = libodbc.SQLColAttribute (hstmt, (short)column, fieldId, buffer, (short)buffer.Length, ref outsize, ref val); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException ( OdbcHandleType.Stmt, hstmt); string value = string.Empty; if (outsize > 0) value = Encoding.Unicode.GetString (buffer, 0, outsize); return value; } private string [] GetPrimaryKeys (string catalog, string schema, string table) { if (cols.Length <= 0) return new string [0]; ArrayList keys = null; try { keys = GetPrimaryKeysBySQLPrimaryKey (catalog, schema, table); } catch (OdbcException) { try { keys = GetPrimaryKeysBySQLStatistics (catalog, schema, table); } catch (OdbcException) { } } if (keys == null) return null; keys.Sort (); return (string []) keys.ToArray (typeof (string)); } private ArrayList GetPrimaryKeysBySQLPrimaryKey (string catalog, string schema, string table) { ArrayList keys = new ArrayList (); IntPtr handle = IntPtr.Zero; OdbcReturn ret; try { ret=libodbc.SQLAllocHandle(OdbcHandleType.Stmt, command.Connection.hDbc, ref handle); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException ( OdbcHandleType.Dbc, Connection.hDbc); ret = libodbc.SQLPrimaryKeys (handle, catalog, -3, schema, -3, table, -3); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); int length = 0; byte [] primaryKey = new byte [255]; ret = libodbc.SQLBindCol (handle, 4, SQL_C_TYPE.CHAR, primaryKey, primaryKey.Length, ref length); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); while (true) { ret = libodbc.SQLFetch (handle); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) break; string pkey = Encoding.Default.GetString (primaryKey, 0, length); keys.Add (pkey); } } finally { if (handle != IntPtr.Zero) { ret = libodbc.SQLFreeStmt (handle, libodbc.SQLFreeStmtOptions.Close); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); ret = libodbc.SQLFreeHandle( (ushort) OdbcHandleType.Stmt, handle); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); } } return keys; } private unsafe ArrayList GetPrimaryKeysBySQLStatistics (string catalog, string schema, string table) { ArrayList keys = new ArrayList (); IntPtr handle = IntPtr.Zero; OdbcReturn ret; try { ret=libodbc.SQLAllocHandle(OdbcHandleType.Stmt, command.Connection.hDbc, ref handle); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException ( OdbcHandleType.Dbc, Connection.hDbc); ret = libodbc.SQLStatistics (handle, catalog, -3, schema, -3, table, -3, libodbc.SQL_INDEX_UNIQUE, libodbc.SQL_QUICK); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); // NON_UNIQUE int nonUniqueLength = 0; short nonUnique = libodbc.SQL_FALSE; ret = libodbc.SQLBindCol (handle, 4, SQL_C_TYPE.SHORT, ref nonUnique, sizeof (short), ref nonUniqueLength); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); // COLUMN_NAME int length = 0; byte [] colName = new byte [255]; ret = libodbc.SQLBindCol (handle, 9, SQL_C_TYPE.CHAR, colName, colName.Length, ref length); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); while (true) { ret = libodbc.SQLFetch (handle); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) break; if (nonUnique == libodbc.SQL_TRUE) { string pkey = Encoding.Default.GetString (colName, 0, length); keys.Add (pkey); break; } } } finally { if (handle != IntPtr.Zero) { ret = libodbc.SQLFreeStmt (handle, libodbc.SQLFreeStmtOptions.Close); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); ret = libodbc.SQLFreeHandle ((ushort) OdbcHandleType.Stmt, handle); if ((ret!=OdbcReturn.Success) && (ret!=OdbcReturn.SuccessWithInfo)) throw Connection.CreateOdbcException (OdbcHandleType.Stmt, handle); } } return keys; } public #if NET_2_0 override #endif // NET_2_0 bool Read () { return NextRow (); } static string RemoveTrailingNullChar (string value) { return value.TrimEnd ('\0'); } #endregion } }