// Copyright (c) 2006-2008 MySQL AB, 2008-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 MySql.Data.Common; using System.Collections.Generic; using System.Text; using System; using System.Data; using System.Globalization; using System.IO; using MySql.Data.MySqlClient.Properties; namespace MySql.Data.MySqlClient { /// /// Provides a class capable of executing a SQL script containing /// multiple SQL statements including CREATE PROCEDURE statements /// that require changing the delimiter /// public class MySqlScript { private MySqlConnection connection; private string query; private string delimiter; public event MySqlStatementExecutedEventHandler StatementExecuted; public event MySqlScriptErrorEventHandler Error; public event EventHandler ScriptCompleted; #region Constructors /// /// Initializes a new instance of the /// class. /// public MySqlScript() { Delimiter = ";"; } /// /// Initializes a new instance of the /// class. /// /// The connection. public MySqlScript(MySqlConnection connection) : this() { this.connection = connection; } /// /// Initializes a new instance of the /// class. /// /// The query. public MySqlScript(string query) : this() { this.query = query; } /// /// Initializes a new instance of the /// class. /// /// The connection. /// The query. public MySqlScript(MySqlConnection connection, string query) :this() { this.connection = connection; this.query = query; } #endregion #region Properties /// /// Gets or sets the connection. /// /// The connection. public MySqlConnection Connection { get { return connection; } set { connection = value; } } /// /// Gets or sets the query. /// /// The query. public string Query { get { return query; } set { query = value; } } /// /// Gets or sets the delimiter. /// /// The delimiter. public string Delimiter { get { return delimiter; } set { delimiter = value; } } #endregion #region Public Methods /// /// Executes this instance. /// /// The number of statements executed as part of the script. public int Execute() { bool openedConnection = false; if (connection == null) throw new InvalidOperationException(Resources.ConnectionNotSet); if (query == null || query.Length == 0) return 0; // next we open up the connetion if it is not already open if (connection.State != ConnectionState.Open) { openedConnection = true; connection.Open(); } // since we don't allow setting of parameters on a script we can // therefore safely allow the use of user variables. no one should be using // this connection while we are using it so we can temporarily tell it // to allow the use of user variables bool allowUserVars = connection.Settings.AllowUserVariables; connection.Settings.AllowUserVariables = true; try { string mode = connection.driver.Property("sql_mode"); mode = mode.ToUpper(CultureInfo.InvariantCulture); bool ansiQuotes = mode.IndexOf("ANSI_QUOTES") != -1; bool noBackslashEscapes = mode.IndexOf("NO_BACKSLASH_ESCAPES") != -1; // first we break the query up into smaller queries List statements = BreakIntoStatements(ansiQuotes, noBackslashEscapes); int count = 0; MySqlCommand cmd = new MySqlCommand(null, connection); foreach (ScriptStatement statement in statements) { if (String.IsNullOrEmpty(statement.text)) continue; cmd.CommandText = statement.text; try { cmd.ExecuteNonQuery(); count++; OnQueryExecuted(statement); } catch (Exception ex) { if (Error == null) throw; if (!OnScriptError(ex)) break; } } OnScriptCompleted(); return count; } finally { connection.Settings.AllowUserVariables = allowUserVars; if (openedConnection) { connection.Close(); } } } #endregion private void OnQueryExecuted(ScriptStatement statement) { if (StatementExecuted != null) { MySqlScriptEventArgs args = new MySqlScriptEventArgs(); args.Statement = statement; StatementExecuted(this, args); } } private void OnScriptCompleted() { if (ScriptCompleted != null) ScriptCompleted(this, EventArgs.Empty); } private bool OnScriptError(Exception ex) { if (Error != null) { MySqlScriptErrorEventArgs args = new MySqlScriptErrorEventArgs(ex); Error(this, args); return args.Ignore; } return false; } private List BreakScriptIntoLines() { List lineNumbers = new List(); StringReader sr = new StringReader(query); string line = sr.ReadLine(); int pos = 0; while (line != null) { lineNumbers.Add(pos); pos += line.Length; line = sr.ReadLine(); } return lineNumbers; } private static int FindLineNumber(int position, List lineNumbers) { int i = 0; while (i < lineNumbers.Count && position < lineNumbers[i]) i++; return i; } private List BreakIntoStatements(bool ansiQuotes, bool noBackslashEscapes) { string currentDelimiter = Delimiter; int startPos = 0; List statements = new List(); List lineNumbers = BreakScriptIntoLines(); MySqlTokenizer tokenizer = new MySqlTokenizer(query); tokenizer.AnsiQuotes = ansiQuotes; tokenizer.BackslashEscapes = !noBackslashEscapes; string token = tokenizer.NextToken(); while (token != null) { if (!tokenizer.Quoted) { if (token.ToLower(CultureInfo.InvariantCulture) == "delimiter") { tokenizer.NextToken(); AdjustDelimiterEnd(tokenizer); currentDelimiter = query.Substring(tokenizer.StartIndex, tokenizer.StopIndex - tokenizer.StartIndex + 1).Trim(); startPos = tokenizer.StopIndex; } else { // this handles the case where our tokenizer reads part of the // delimiter if (currentDelimiter.StartsWith(token)) { if ((tokenizer.StartIndex + currentDelimiter.Length) <= query.Length) { if (query.Substring(tokenizer.StartIndex, currentDelimiter.Length) == currentDelimiter) { token = currentDelimiter; tokenizer.Position = tokenizer.StartIndex + currentDelimiter.Length; tokenizer.StopIndex = tokenizer.Position; } } } int delimiterPos = token.IndexOf(currentDelimiter, StringComparison.InvariantCultureIgnoreCase); if (delimiterPos != -1) { int endPos = tokenizer.StopIndex - token.Length + delimiterPos; if (tokenizer.StopIndex == query.Length - 1) endPos++; string currentQuery = query.Substring(startPos, endPos - startPos); ScriptStatement statement = new ScriptStatement(); statement.text = currentQuery.Trim(); statement.line = FindLineNumber(startPos, lineNumbers); statement.position = startPos - lineNumbers[statement.line]; statements.Add(statement); startPos = endPos + currentDelimiter.Length; } } } token = tokenizer.NextToken(); } // now clean up the last statement if (startPos < query.Length-1) { string sqlLeftOver = query.Substring(startPos).Trim(); if (!String.IsNullOrEmpty(sqlLeftOver)) { ScriptStatement statement = new ScriptStatement(); statement.text = sqlLeftOver; statement.line = FindLineNumber(startPos, lineNumbers); statement.position = startPos - lineNumbers[statement.line]; statements.Add(statement); } } return statements; } private void AdjustDelimiterEnd(MySqlTokenizer tokenizer) { int pos = tokenizer.StopIndex; char c = query[pos]; while (!Char.IsWhiteSpace(c) && pos < (query.Length-1)) { c = query[++pos]; } tokenizer.StopIndex = pos; tokenizer.Position = pos; } } /// /// /// public delegate void MySqlStatementExecutedEventHandler(object sender, MySqlScriptEventArgs args); /// /// /// public delegate void MySqlScriptErrorEventHandler(object sender, MySqlScriptErrorEventArgs args); /// /// /// public class MySqlScriptEventArgs : EventArgs { private ScriptStatement statement; internal ScriptStatement Statement { set { this.statement = value; } } /// /// Gets the statement text. /// /// The statement text. public string StatementText { get { return statement.text; } } /// /// Gets the line. /// /// The line. public int Line { get { return statement.line; } } /// /// Gets the position. /// /// The position. public int Position { get { return statement.position; } } } /// /// /// public class MySqlScriptErrorEventArgs : MySqlScriptEventArgs { private Exception exception; private bool ignore; /// /// Initializes a new instance of the class. /// /// The exception. public MySqlScriptErrorEventArgs(Exception exception) : base() { this.exception = exception; } /// /// Gets the exception. /// /// The exception. public Exception Exception { get { return exception; } } /// /// Gets or sets a value indicating whether this is ignore. /// /// true if ignore; otherwise, false. public bool Ignore { get { return ignore; } set { ignore = value; } } } struct ScriptStatement { public string text; public int line; public int position; } }