#if MYSQL_6_9 // Copyright © 2009, 2013, Oracle and/or its affiliates. All rights reserved. // // MySQL Connector/NET is licensed under the terms of the GPLv2 // , like most // MySQL Connectors. There are special exceptions to the terms and // conditions of the GPLv2 as it is applied to this software, see the // FLOSS License Exception // . // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published // by the Free Software Foundation; version 2 of the License. // // 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., // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA using System; using System.Collections; using System.Data; using Externals.MySql.Data.MySqlClient.Properties; using Externals.MySql.Data.Types; using System.Diagnostics; using System.Collections.Generic; namespace Externals.MySql.Data.MySqlClient { internal class ResultSet { private Driver driver; private bool hasRows; private bool[] uaFieldsUsed; private MySqlField[] fields; private IMySqlValue[] values; private Dictionary fieldHashCS; private Dictionary fieldHashCI; private int rowIndex; private bool readDone; private bool isSequential; private int seqIndex; private bool isOutputParameters; private int affectedRows; private long insertedId; private int statementId; private int totalRows; private int skippedRows; private bool cached; private List cachedValues; public ResultSet(int affectedRows, long 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 = IsOutputParameterResultSet(); 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 long InsertedId { get { return insertedId; } } public int TotalRows { get { return totalRows; } } public int SkippedRows { get { return skippedRows; } } public bool Cached { get { return cached; } set { cached = value; if (cached && cachedValues == null) cachedValues = new List(); } } #endregion /// /// return the ordinal for the given column name /// /// /// public int GetOrdinal(string name) { // first we try a quick hash lookup int ordinal; if (fieldHashCS.TryGetValue(name, out ordinal)) return ordinal; // ok that failed so we use our CI hash if (fieldHashCI.TryGetValue( name, out ordinal )) return 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) { if (Cached) return CachedNextRow(behavior); 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.IsQueryAborted) { // avoid hanging on Close() readDone = true; } throw; } if (!fetched) { readDone = true; return false; } } if (!isSequential) ReadColumnData(false); rowIndex++; return true; } private bool CachedNextRow(CommandBehavior behavior) { if ((behavior & CommandBehavior.SingleRow) != 0 && rowIndex == 0) return false; if (rowIndex == (totalRows - 1)) return false; rowIndex++; values = cachedValues[rowIndex]; return true; } /// /// Closes the current resultset, dumping any data still on the wire /// public void Close() { if (!readDone) { // if we have rows but the user didn't read the first one then mark it as skipped if (HasRows && rowIndex == -1) skippedRows++; try { while (driver.IsOpen && driver.SkipDataRow()) { totalRows++; skippedRows++; } } catch (System.IO.IOException) { // it is ok to eat IO exceptions here, we just want to // close the result set } readDone = true; } else if (driver == null) CacheClose(); driver = null; if (Cached) CacheReset(); } private void CacheClose() { skippedRows = totalRows - rowIndex - 1; } private void CacheReset() { if (!Cached) return; rowIndex = -1; affectedRows = -1; insertedId = -1; skippedRows = 0; } 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; } private bool IsOutputParameterResultSet() { if (driver.HasStatus(ServerStatusFlags.OutputParameters)) return true; if (fields.Length == 0) return false; for (int x = 0; x < fields.Length; x++) if (!fields[x].ColumnName.StartsWith("@" + StoredProcedure.ParameterPrefix, StringComparison.OrdinalIgnoreCase)) return false; return true; } /// /// 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 Dictionary(StringComparer.OrdinalIgnoreCase); fieldHashCI = new Dictionary(StringComparer.OrdinalIgnoreCase); 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 we are caching then we need to save a copy of this row of data values if (Cached) cachedValues.Add((IMySqlValue[])values.Clone()); // we don't need to worry about caching the following since you won't have output // params with TableDirect commands if (outputParms) { bool rowExists = driver.FetchDataRow(statementId, fields.Length); rowIndex = 0; if (rowExists) throw new MySqlException(Resources.MoreThanOneOPRow); } } } } #endif