// Copyright (c) 2009 Sun Microsystems, Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as published by // the Free Software Foundation // // There are special exceptions to the terms and conditions of the GPL // as it is applied to this software. View the full text of the // exception in file EXCEPTIONS in the directory of this software // distribution. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Collections; using System.Data; using MySql.Data.MySqlClient.Properties; using MySql.Data.Types; using System.Diagnostics; namespace MySql.Data.MySqlClient { internal class ResultSet { private Driver driver; private bool hasRows; private bool[] uaFieldsUsed; private MySqlField[] fields; private IMySqlValue[] values; private Hashtable fieldHashCS; private Hashtable fieldHashCI; private int rowIndex; private bool readDone; private bool isSequential; private int seqIndex; private bool isOutputParameters; private int affectedRows; private int insertedId; private int statementId; private int totalRows; private int skippedRows; public ResultSet(int affectedRows, int insertedId) { this.affectedRows = affectedRows; this.insertedId = insertedId; readDone = true; } public ResultSet(Driver d, int statementId, int numCols) { affectedRows = -1; insertedId = -1; driver = d; this.statementId = statementId; rowIndex = -1; LoadColumns(numCols); isOutputParameters = driver.HasStatus(ServerStatusFlags.OutputParameters); hasRows = GetNextRow(); readDone = !hasRows; } #region Properties public bool HasRows { get { return hasRows; } } public int Size { get { return fields == null ? 0 : fields.Length; } } public MySqlField[] Fields { get { return fields; } } public IMySqlValue[] Values { get { return values; } } public bool IsOutputParameters { get { return isOutputParameters; } set { isOutputParameters = value; } } public int AffectedRows { get { return affectedRows; } } public int InsertedId { get { return insertedId; } } public int TotalRows { get { return totalRows; } } public int SkippedRows { get { return skippedRows; } } #endregion /// /// return the ordinal for the given column name /// /// /// public int GetOrdinal(string name) { // first we try a quick hash lookup object ordinal = fieldHashCS[name]; if (ordinal != null) return (int)ordinal; // ok that failed so we use our CI hash ordinal = fieldHashCI[name]; if (ordinal != null) return (int)ordinal; // Throw an exception if the ordinal cannot be found. throw new IndexOutOfRangeException( String.Format(Resources.CouldNotFindColumnName, name)); } /// /// Retrieve the value as the given column index /// /// The column value to retrieve /// The value as the given column public IMySqlValue this[int index] { get { if (rowIndex < 0) throw new MySqlException(Resources.AttemptToAccessBeforeRead); // keep count of how many columns we have left to access uaFieldsUsed[index] = true; if (isSequential && index != seqIndex) { if (index < seqIndex) throw new MySqlException(Resources.ReadingPriorColumnUsingSeqAccess); while (seqIndex < (index - 1)) driver.SkipColumnValue(values[++seqIndex]); values[index] = driver.ReadColumnValue(index, fields[index], values[index]); seqIndex = index; } return values[index]; } } private bool GetNextRow() { bool fetched = driver.FetchDataRow(statementId, Size); if (fetched) totalRows++; return fetched; } public bool NextRow(CommandBehavior behavior) { if (readDone) return false; if ((behavior & CommandBehavior.SingleRow) != 0 && rowIndex == 0) return false; isSequential = (behavior & CommandBehavior.SequentialAccess) != 0; seqIndex = -1; // if we are at row index >= 0 then we need to fetch the data row and load it if (rowIndex >= 0) { bool fetched = false; try { fetched = GetNextRow(); } catch (MySqlException ex) { if (ex.Number == 1317) fetched = false; } if (!fetched) { readDone = true; return false; } } if (!isSequential) ReadColumnData(false); rowIndex++; return true; } /// /// Closes the current resultset, dumping any data still on the wire /// public void Close() { if (readDone) return; // if we have rows but the user didn't read the first one then mark it as skipped if (HasRows && rowIndex == -1) skippedRows++; while (driver.SkipDataRow()) { totalRows++; skippedRows++; } readDone = true; } public bool FieldRead(int index) { Debug.Assert(Size > index); return uaFieldsUsed[index]; } public void SetValueObject(int i, IMySqlValue valueObject) { Debug.Assert(values != null); Debug.Assert(i < values.Length); values[i] = valueObject; } /// /// Loads the column metadata for the current resultset /// private void LoadColumns(int numCols) { fields = driver.GetColumns(numCols); values = new IMySqlValue[numCols]; uaFieldsUsed = new bool[numCols]; fieldHashCS = new Hashtable(); fieldHashCI = new Hashtable(StringComparer.InvariantCultureIgnoreCase); for (int i = 0; i < fields.Length; i++) { string columnName = fields[i].ColumnName; if (!fieldHashCS.ContainsKey(columnName)) fieldHashCS.Add(columnName, i); if (!fieldHashCI.ContainsKey(columnName)) fieldHashCI.Add(columnName, i); values[i] = fields[i].GetValueObject(); } } private void ReadColumnData(bool outputParms) { for (int i = 0; i < Size; i++) values[i] = driver.ReadColumnValue(i, fields[i], values[i]); if (outputParms) { bool rowExists = driver.FetchDataRow(statementId, fields.Length); rowIndex = 0; if (rowExists) throw new MySqlException(Resources.MoreThanOneOPRow); } } } }