// Copyright (C) 2004 MySQL AB
//
// 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.ComponentModel;
using System.Data;
using System.Text;
namespace MySql.Data.MySqlClient
{
///
[ToolboxItem(false)]
[System.ComponentModel.DesignerCategory("Code")]
public sealed class MySqlCommandBuilder : Component
{
private MySqlDataAdapter _adapter;
private string _QuotePrefix;
private string _QuoteSuffix;
private DataTable _schema;
private string _tableName;
private string _schemaName;
private MySqlCommand _updateCmd;
private MySqlCommand _insertCmd;
private MySqlCommand _deleteCmd;
private char marker = '?';
private bool lastOneWins;
private string TableName
{
get
{
if (_schemaName != null)
return Quote(_schemaName) + "." + Quote(_tableName);
return Quote(_tableName);
}
}
#region Constructors
///
public MySqlCommandBuilder()
{
_QuotePrefix = _QuoteSuffix = "`";
this.lastOneWins = false;
}
///
public MySqlCommandBuilder(bool lastOneWins)
{
_QuotePrefix = _QuoteSuffix = "`";
this.lastOneWins = lastOneWins;
}
///
public MySqlCommandBuilder( MySqlDataAdapter adapter ) : this()
{
DataAdapter = adapter;
}
///
public MySqlCommandBuilder( MySqlDataAdapter adapter, bool lastOneWins ) : this(lastOneWins)
{
DataAdapter = adapter;
}
#endregion
#region Properties
///
public MySqlDataAdapter DataAdapter
{
get { return _adapter; }
set
{
if (_adapter != null)
{
_adapter.RowUpdating -= new MySqlRowUpdatingEventHandler( OnRowUpdating );
}
_adapter = value;
_adapter.RowUpdating += new MySqlRowUpdatingEventHandler( OnRowUpdating );
}
}
///
public string QuotePrefix
{
get { return _QuotePrefix; }
set { _QuotePrefix = value; }
}
///
public string QuoteSuffix
{
get { return _QuoteSuffix; }
set { _QuoteSuffix = value; }
}
#endregion
#region Public Methods
///
/// Retrieves parameter information from the stored procedure specified in the MySqlCommand and populates the Parameters collection of the specified MySqlCommand object.
/// This method is not currently supported since stored procedures are not available in MySql.
///
/// The MySqlCommand referencing the stored procedure from which the parameter information is to be derived. The derived parameters are added to the Parameters collection of the MySqlCommand.
/// The command text is not a valid stored procedure name.
public static void DeriveParameters(MySqlCommand command)
{
throw new MySqlException("DeriveParameters is not supported (due to MySql not supporting SP)");
}
///
public MySqlCommand GetDeleteCommand()
{
if (_schema == null)
GenerateSchema();
return CreateDeleteCommand();
}
///
public MySqlCommand GetInsertCommand()
{
if (_schema == null)
GenerateSchema();
return CreateInsertCommand();
}
///
public MySqlCommand GetUpdateCommand()
{
if (_schema == null)
GenerateSchema();
return CreateUpdateCommand();
}
///
public void RefreshSchema()
{
_schema = null;
_insertCmd = null;
_deleteCmd = null;
_updateCmd = null;
_tableName = null;
_schemaName = null;
}
#endregion
#region Private Methods
private void GenerateSchema()
{
// set the parameter marker
MySqlConnection conn = (MySqlConnection)_adapter.SelectCommand.Connection;
marker = conn.ParameterMarker;
if (_adapter == null)
throw new MySqlException("Improper MySqlCommandBuilder state: adapter is null");
if (_adapter.SelectCommand == null)
throw new MySqlException("Improper MySqlCommandBuilder state: adapter's SelectCommand is null");
MySqlDataReader dr = _adapter.SelectCommand.ExecuteReader(CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
_schema = dr.GetSchemaTable();
dr.Close();
// make sure we got at least one unique or key field and count base table names
bool hasKeyOrUnique=false;
foreach (DataRow row in _schema.Rows)
{
if (true == (bool)row["IsKey"] || true == (bool)row["IsUnique"])
hasKeyOrUnique=true;
if (_tableName == null)
{
_schemaName = (string)row["BaseSchemaName"];
_tableName = (string)row["BaseTableName"];
}
else if (_tableName != (string)row["BaseTableName"] || _schemaName != (string)row["BaseSchemaName"])
throw new InvalidOperationException("MySqlCommandBuilder does not support multi-table statements");
}
if (! hasKeyOrUnique)
throw new InvalidOperationException("MySqlCommandBuilder cannot operate on tables with no unique or key columns");
}
private string Quote(string table_or_column)
{
if (_QuotePrefix == null || _QuoteSuffix == null)
return table_or_column;
return _QuotePrefix + table_or_column + _QuoteSuffix;
}
private string GetParameterName( string columnName )
{
string colName = columnName.Replace(" ", "");
return colName;
}
private MySqlParameter CreateParameter(DataRow row, bool Original)
{
MySqlParameter p;
string colName = GetParameterName( row["ColumnName"].ToString() );
MySqlDbType type = (MySqlDbType)row["ProviderType"];
if (Original)
p = new MySqlParameter( "Original_" + colName, type, ParameterDirection.Input,
(string)row["ColumnName"], DataRowVersion.Original, DBNull.Value );
else
p = new MySqlParameter( colName, type, ParameterDirection.Input,
(string)row["ColumnName"], DataRowVersion.Current, DBNull.Value );
return p;
}
private MySqlCommand CreateBaseCommand()
{
MySqlCommand cmd = new MySqlCommand();
cmd.Connection = _adapter.SelectCommand.Connection;
cmd.CommandTimeout = _adapter.SelectCommand.CommandTimeout;
cmd.Transaction = _adapter.SelectCommand.Transaction;
return cmd;
}
private MySqlCommand CreateDeleteCommand()
{
if (_deleteCmd != null) return _deleteCmd;
MySqlCommand cmd = CreateBaseCommand();
cmd.CommandText = "DELETE FROM " + TableName +
" WHERE " + CreateOriginalWhere(cmd);
_deleteCmd = cmd;
return cmd;
}
private string CreateFinalSelect(bool forinsert)
{
StringBuilder sel = new StringBuilder();
StringBuilder where = new StringBuilder();
foreach (DataRow row in _schema.Rows)
{
string colname = Quote(row["ColumnName"].ToString());
string parmName = GetParameterName( row["ColumnName"].ToString() );
if (sel.Length > 0)
sel.Append(", ");
sel.Append( colname );
if ((bool)row["IsKey"] == false) continue;
if (where.Length > 0)
where.Append(" AND ");
where.Append( "(" + colname + "=" );
if (forinsert)
{
if ((bool)row["IsAutoIncrement"])
where.Append("last_insert_id()");
else if ((bool)row["IsKey"])
where.Append( marker + parmName);
}
else
{
where.Append(marker + "Original_" + parmName);
}
where.Append(")");
}
return "SELECT " + sel.ToString() + " FROM " + TableName +
" WHERE " + where.ToString();
}
private string CreateOriginalWhere(MySqlCommand cmd)
{
StringBuilder wherestr = new StringBuilder();
foreach (DataRow row in _schema.Rows)
{
// if we are doing last one wins and this column is not a key or is not
// unique, then we don't care about it
if (true != (bool)row["IsKey"] && true != (bool)row["IsUnique"] && lastOneWins)
continue;
if (! IncludedInWhereClause(row)) continue;
// first update the where clause since it will contain all parameters
// if (wherestr.Length > 0)
// wherestr.Append(" AND ");
string colname = Quote((string)row["ColumnName"]);
MySqlParameter op = CreateParameter(row, true);
cmd.Parameters.Add(op);
wherestr.Append( colname + " <=> " + marker + op.ParameterName + " AND ");
// if ((bool)row["AllowDBNull"] == true)
// wherestr.Append( " or (" + colname + " IS NULL and ?" + op.ParameterName + " IS NULL)");
//wherestr.Append(")");
}
wherestr.Remove( wherestr.Length-5, 5 ); // remove the trailling " AND "
return wherestr.ToString();
}
private MySqlCommand CreateUpdateCommand()
{
if (_updateCmd != null) return _updateCmd;
MySqlCommand cmd = CreateBaseCommand();
StringBuilder setstr = new StringBuilder();
foreach (DataRow schemaRow in _schema.Rows)
{
string colname = Quote((string)schemaRow["ColumnName"]);
if (! IncludedInUpdate(schemaRow)) continue;
if (setstr.Length > 0)
setstr.Append(", ");
MySqlParameter p = CreateParameter(schemaRow, false);
cmd.Parameters.Add(p);
setstr.Append( colname + "=" + marker + p.ParameterName );
}
cmd.CommandText = "UPDATE " + TableName + " SET " + setstr.ToString() +
" WHERE " + CreateOriginalWhere(cmd);
cmd.CommandText += "; " + CreateFinalSelect(false);
_updateCmd = cmd;
return cmd;
}
private MySqlCommand CreateInsertCommand()
{
if (_insertCmd != null) return _insertCmd;
MySqlCommand cmd = CreateBaseCommand();
StringBuilder setstr = new StringBuilder();
StringBuilder valstr = new StringBuilder();
foreach (DataRow schemaRow in _schema.Rows)
{
string colname = Quote((string)schemaRow["ColumnName"]);
if (!IncludedInInsert(schemaRow)) continue;
if (setstr.Length > 0)
{
setstr.Append(", ");
valstr.Append(", ");
}
MySqlParameter p = CreateParameter(schemaRow, false);
cmd.Parameters.Add(p);
setstr.Append( colname );
valstr.Append( marker + p.ParameterName );
}
cmd.CommandText = "INSERT INTO " + TableName + " (" + setstr.ToString() + ") " +
" VALUES (" + valstr.ToString() + ")";
cmd.CommandText += "; " + CreateFinalSelect(true);
_insertCmd = cmd;
return cmd;
}
private bool IncludedInInsert (DataRow schemaRow)
{
// If the parameter has one of these properties, then we don't include it in the insert:
// AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
if ((bool) schemaRow ["IsAutoIncrement"])
return false;
/* if ((bool) schemaRow ["IsHidden"])
return false;
if ((bool) schemaRow ["IsExpression"])
return false;*/
if ((bool) schemaRow ["IsRowVersion"])
return false;
if ((bool) schemaRow ["IsReadOnly"])
return false;
return true;
}
private bool IncludedInUpdate (DataRow schemaRow)
{
// If the parameter has one of these properties, then we don't include it in the insert:
// AutoIncrement, Hidden, RowVersion
if ((bool) schemaRow ["IsAutoIncrement"])
return false;
// if ((bool) schemaRow ["IsHidden"])
// return false;
if ((bool) schemaRow ["IsRowVersion"])
return false;
return true;
}
private bool IncludedInWhereClause (DataRow schemaRow)
{
// if ((bool) schemaRow ["IsLong"])
// return false;
return true;
}
private void SetParameterValues(MySqlCommand cmd, DataRow dataRow)
{
foreach (MySqlParameter p in cmd.Parameters)
{
if (p.SourceVersion == DataRowVersion.Original)
// if (p.ParameterName.Length >= 8 && p.ParameterName.Substring(0, 8).Equals("Original"))
p.Value = dataRow[ p.SourceColumn, DataRowVersion.Original ];
else
p.Value = dataRow[ p.SourceColumn, DataRowVersion.Current ];
}
}
private void OnRowUpdating(object sender, MySqlRowUpdatingEventArgs args)
{
// make sure we are still to proceed
if (args.Status != UpdateStatus.Continue) return;
if (_schema == null)
GenerateSchema();
if (StatementType.Delete == args.StatementType)
args.Command = CreateDeleteCommand();
else if (StatementType.Update == args.StatementType)
args.Command = CreateUpdateCommand();
else if (StatementType.Insert == args.StatementType)
args.Command = CreateInsertCommand();
else if (StatementType.Select == args.StatementType)
return;
SetParameterValues(args.Command, args.Row);
}
#endregion
}
}