// 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);
}
}
}
}