You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
483 lines
13 KiB
483 lines
13 KiB
#if MYSQL_6_9
|
|
|
|
// Copyright ?2004, 2014, Oracle and/or its affiliates. All rights reserved.
|
|
//
|
|
// MySQL Connector/NET is licensed under the terms of the GPLv2
|
|
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, 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
|
|
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
|
|
//
|
|
// 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 Externals.MySql.Data.Common;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using Externals.MySql.Data.MySqlClient.Properties;
|
|
using System.Threading.Tasks;
|
|
using System.Threading;
|
|
|
|
namespace Externals.MySql.Data.MySqlClient
|
|
{
|
|
/// <summary>
|
|
/// Provides a class capable of executing a SQL script containing
|
|
/// multiple SQL statements including CREATE PROCEDURE statements
|
|
/// that require changing the delimiter
|
|
/// </summary>
|
|
internal 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
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="MySqlScript"/> class.
|
|
/// </summary>
|
|
public MySqlScript()
|
|
{
|
|
Delimiter = ";";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="MySqlScript"/> class.
|
|
/// </summary>
|
|
/// <param name="connection">The connection.</param>
|
|
public MySqlScript(MySqlConnection connection)
|
|
: this()
|
|
{
|
|
this.connection = connection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="MySqlScript"/> class.
|
|
/// </summary>
|
|
/// <param name="query">The query.</param>
|
|
public MySqlScript(string query)
|
|
: this()
|
|
{
|
|
this.query = query;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="MySqlScript"/> class.
|
|
/// </summary>
|
|
/// <param name="connection">The connection.</param>
|
|
/// <param name="query">The query.</param>
|
|
public MySqlScript(MySqlConnection connection, string query)
|
|
: this()
|
|
{
|
|
this.connection = connection;
|
|
this.query = query;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the connection.
|
|
/// </summary>
|
|
/// <value>The connection.</value>
|
|
public MySqlConnection Connection
|
|
{
|
|
get { return connection; }
|
|
set { connection = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the query.
|
|
/// </summary>
|
|
/// <value>The query.</value>
|
|
public string Query
|
|
{
|
|
get { return query; }
|
|
set { query = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the delimiter.
|
|
/// </summary>
|
|
/// <value>The delimiter.</value>
|
|
public string Delimiter
|
|
{
|
|
get { return delimiter; }
|
|
set { delimiter = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Executes this instance.
|
|
/// </summary>
|
|
/// <returns>The number of statements executed as part of the script.</returns>
|
|
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 = StringUtility.ToUpperInvariant(mode);
|
|
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<ScriptStatement> 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<int> BreakScriptIntoLines()
|
|
{
|
|
List<int> lineNumbers = new List<int>();
|
|
|
|
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<int> lineNumbers)
|
|
{
|
|
int i = 0;
|
|
while (i < lineNumbers.Count && position < lineNumbers[i])
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
private List<ScriptStatement> BreakIntoStatements(bool ansiQuotes, bool noBackslashEscapes)
|
|
{
|
|
string currentDelimiter = Delimiter;
|
|
int startPos = 0;
|
|
List<ScriptStatement> statements = new List<ScriptStatement>();
|
|
List<int> 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).Trim();
|
|
startPos = tokenizer.StopIndex;
|
|
}
|
|
else
|
|
{
|
|
// this handles the case where our tokenizer reads part of the
|
|
// delimiter
|
|
if (currentDelimiter.StartsWith(token, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
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.OrdinalIgnoreCase);
|
|
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)
|
|
{
|
|
if (tokenizer.StopIndex < query.Length)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
#region Async
|
|
/// <summary>
|
|
/// Async version of Execute
|
|
/// </summary>
|
|
/// <returns>The number of statements executed as part of the script inside.</returns>
|
|
public Task<int> ExecuteAsync()
|
|
{
|
|
return ExecuteAsync(CancellationToken.None);
|
|
}
|
|
|
|
public Task<int> ExecuteAsync(CancellationToken cancellationToken)
|
|
{
|
|
var result = new TaskCompletionSource<int>();
|
|
if (cancellationToken == CancellationToken.None || !cancellationToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
var executeResult = Execute();
|
|
result.SetResult(executeResult);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.SetException(ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.SetCanceled();
|
|
}
|
|
return result.Task;
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
internal delegate void MySqlStatementExecutedEventHandler(object sender, MySqlScriptEventArgs args);
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
internal delegate void MySqlScriptErrorEventHandler(object sender, MySqlScriptErrorEventArgs args);
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
internal class MySqlScriptEventArgs : EventArgs
|
|
{
|
|
private ScriptStatement statement;
|
|
|
|
internal ScriptStatement Statement
|
|
{
|
|
set { this.statement = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the statement text.
|
|
/// </summary>
|
|
/// <value>The statement text.</value>
|
|
public string StatementText
|
|
{
|
|
get { return statement.text; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the line.
|
|
/// </summary>
|
|
/// <value>The line.</value>
|
|
public int Line
|
|
{
|
|
get { return statement.line; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the position.
|
|
/// </summary>
|
|
/// <value>The position.</value>
|
|
public int Position
|
|
{
|
|
get { return statement.position; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
internal class MySqlScriptErrorEventArgs : MySqlScriptEventArgs
|
|
{
|
|
private Exception exception;
|
|
private bool ignore;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="MySqlScriptErrorEventArgs"/> class.
|
|
/// </summary>
|
|
/// <param name="exception">The exception.</param>
|
|
public MySqlScriptErrorEventArgs(Exception exception)
|
|
: base()
|
|
{
|
|
this.exception = exception;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the exception.
|
|
/// </summary>
|
|
/// <value>The exception.</value>
|
|
public Exception Exception
|
|
{
|
|
get { return exception; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether this <see cref="MySqlScriptErrorEventArgs"/> is ignore.
|
|
/// </summary>
|
|
/// <value><c>true</c> if ignore; otherwise, <c>false</c>.</value>
|
|
public bool Ignore
|
|
{
|
|
get { return ignore; }
|
|
set { ignore = value; }
|
|
}
|
|
}
|
|
|
|
struct ScriptStatement
|
|
{
|
|
public string text;
|
|
public int line;
|
|
public int position;
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|