// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.BatchParser.ExecutionEngineCode { /// /// Execution engine class which executed the parsed batches /// internal class ExecutionEngine : IDisposable { #region Private fields private OnErrorAction errorAction = OnErrorAction.Ignore; private int numBatchExecutionTimes = 1; private IDbConnection connection = null; private bool isSqlCmdConnection; private Parser commandParser = null; private int executionTimeout; private int startingLine; private ExecutionState executionState = ExecutionState.Initial; private string script; private ScriptExecutionResult result = ScriptExecutionResult.Failure; private bool isLocalParse; private ExecutionEngineConditions conditions = null; private IList preConditionBatches = new List(); private IList postConditionBatches = new List(); private IBatchEventsHandler batchEventHandlers = null; private Batch currentBatch = new Batch(); private ShowPlanType expectedShowPlan; private int currentBatchIndex = -1; private int scriptTrackingId = 1; private object stateSyncLock = new object(); /// /// The internal variables that can be used in SqlCommand substitution. /// These variables take precedence over environment variables. /// private Dictionary internalVariables = new Dictionary(StringComparer.CurrentCultureIgnoreCase); #endregion #region Private members /// /// Batch to be executed /// /// Batch to execute /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] private ScriptExecutionResult DoBatchExecution(Batch batch) { Validate.IsNotNull(nameof(batch), batch); ScriptExecutionResult result = ScriptExecutionResult.Success; // TODO, fawinter: Do I need to keep this batch? if (batch.HasValidText) { try { if (conditions.IsParseOnly) { numBatchExecutionTimes = 1; } int timesLoop = numBatchExecutionTimes; if (numBatchExecutionTimes > 1) { RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_InitializingLoop)); } while (timesLoop > 0 && result != ScriptExecutionResult.Cancel && result != ScriptExecutionResult.Halted) { result = batch.Execute(connection, expectedShowPlan); Debug.Assert(connection != null); if (connection == null || connection.State != ConnectionState.Open) { result = ScriptExecutionResult.Halted; } if (result == ScriptExecutionResult.Failure) { if (errorAction == OnErrorAction.Ignore) { if (numBatchExecutionTimes > 1) { RaiseBatchMessage(SR.EE_BatchExecutionError_Ignoring); } } else { RaiseBatchMessage(SR.EE_BatchExecutionError_Halting); result = ScriptExecutionResult.Halted; } } timesLoop--; } if (result == ScriptExecutionResult.Cancel) { RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_QueryCancelledbyUser)); } else { if (numBatchExecutionTimes > 1) { RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, SR.EE_ExecutionInfo_FinalizingLoop, numBatchExecutionTimes)); } } } catch (OutOfMemoryException) { throw; } catch (Exception ex) { // if anything goes wrong it will shutdown VS Logger.Write(TraceEventType.Error, "Exception Caught in ExecutionEngine.DoBatchExecution(Batch) :" + ex.ToString()); result = ScriptExecutionResult.Failure; } } else { // TODO, fawinter: Success will be returned on an Empty text batch } return result; } /// /// Resets the script's related fields /// /// /// Once the execution thread is nulled, all handles will be closed and GC will collect it /// private void ResetScript() { lock (stateSyncLock) { executionState = ExecutionState.Initial; } ConfigurePrePostConditionBatches(preConditionBatches); ConfigurePrePostConditionBatches(postConditionBatches); currentBatchIndex = -1; conditions = null; batchEventHandlers = null; } /// /// Configures the script for execution /// private void ConfigureBatchParser() { BatchParser batchParser; bool sqlCmdMode; if (conditions != null && conditions.IsSqlCmd) { BatchParserSqlCmd batchParserSqlCmd = new BatchParserSqlCmd(); batchParserSqlCmd.ConnectionChanged = new BatchParserSqlCmd.ConnectionChangedDelegate(OnConnectionChanged); batchParserSqlCmd.ErrorActionChanged = new BatchParserSqlCmd.ErrorActionChangedDelegate(OnErrorActionChanged); batchParserSqlCmd.InternalVariables = internalVariables; sqlCmdMode = true; batchParser = batchParserSqlCmd; } else { batchParser = new BatchParser(); sqlCmdMode = false; } commandParser = new Parser(batchParser, batchParser, new StringReader(script), "[script]"); commandParser.SetRecognizeSqlCmdSyntax(sqlCmdMode); commandParser.SetBatchDelimiter(BatchSeparator); commandParser.ThrowOnUnresolvedVariable = true; batchParser.Execute = new BatchParser.ExecuteDelegate(ExecuteBatchInternal); batchParser.ErrorMessage = new BatchParser.ScriptErrorDelegate(RaiseScriptError); batchParser.Message = new BatchParser.ScriptMessageDelegate(RaiseBatchMessage); batchParser.HaltParser = new BatchParser.HaltParserDelegate(OnHaltParser); batchParser.StartingLine = startingLine; if (isLocalParse) { batchParser.DisableVariableSubstitution(); } } /// /// Configures the batch before execution /// private void ConfigureBatch() { numBatchExecutionTimes = 1; currentBatch.IsResultsExpected = true; } /// /// Called when batch parser found an error /// /// private void RaiseBatchParserExecutionError(string errorLine, string message, ScriptMessageType messageType) { EventHandler cache = BatchParserExecutionError; if (cache != null) { BatchParserExecutionErrorEventArgs args = new BatchParserExecutionErrorEventArgs(errorLine, message, messageType); cache(this, args); } } /// /// Called just after the script has been executed /// /// scipt execution result private void RaiseScriptExecutionFinished(ScriptExecutionResult result) { try { DisconnectSqlCmdInternal(); ConfigureBatchEventHandlers(currentBatch, batchEventHandlers, false); ResetScript(); } finally { EventHandler cache = ScriptExecutionFinished; if (cache != null) { ScriptExecutionFinishedEventArgs args = new ScriptExecutionFinishedEventArgs(result); cache(this, args); } } } /// /// Called when the script parsing has errors/warnings /// /// /// private void RaiseScriptError(string message, ScriptMessageType messageType) { switch (messageType) { case (ScriptMessageType.FatalError): RaiseBatchParserExecutionError(SR.EE_ScriptError_FatalError, message, messageType); break; case (ScriptMessageType.Error): RaiseBatchParserExecutionError(SR.EE_ScriptError_Error, message, messageType); break; default: Debug.Assert(messageType == ScriptMessageType.Warning); RaiseBatchParserExecutionError(SR.EE_ScriptError_Warning, message, messageType); break; } } /// /// Called just after batch has been executed /// /// /// private void RaiseBatchParserExecutionFinished(Batch batch, ScriptExecutionResult batchResult) { Debug.Assert(batch != null); EventHandler cache = BatchParserExecutionFinished; if (cache != null) { BatchParserExecutionFinishedEventArgs args = new BatchParserExecutionFinishedEventArgs(batchResult, batch); cache(this, args); } } /// /// Called right before a batch is executed /// /// /// private void RaiseBatchParserExecutionStarted(Batch batch, TextSpan textSpan) { Debug.Assert(batch != null); EventHandler cache = BatchParserExecutionStart; if (cache != null) { // TODO, fawinter: Get the batch line number as a parameter and pass it in BatchParserExecutionStartEventArgs args = new BatchParserExecutionStartEventArgs(textSpan, batch); cache(this, args); } } /// /// Called when a message needs to be notified to the consumer /// /// private void RaiseBatchMessage(string message) { Validate.IsNotNullOrEmptyString(nameof(message), message); if (batchEventHandlers != null) { BatchMessageEventArgs args = new BatchMessageEventArgs(message); batchEventHandlers.OnBatchMessage(this, args); } } /// /// Executes a given batch given the number of times /// /// /// /// /// True if we should continue processing, false otherwise private bool ExecuteBatchInternal( string batchScript, int num, int lineNumber) { if (lineNumber == -1) { //it means that there was not a single sqlcmd command, //including Batch Delimiter (i.e.) "GO" at the end of the batch. //it should be adjusted it to be the very first line in this case lineNumber = 0; } TextSpan localTextSpan = new TextSpan(); localTextSpan.iStartLine = lineNumber; if (!String.IsNullOrEmpty(batchScript)) { bool continueProcessing = true; numBatchExecutionTimes = num; ExecuteBatchTextSpanInternal(batchScript, localTextSpan, out continueProcessing); return continueProcessing; } else { return true; } } /// /// Executes the batch text given the text span /// /// /// /// private void ExecuteBatchTextSpanInternal(string batchScript, TextSpan textSpan, out bool continueProcessing) { Debug.Assert(!String.IsNullOrEmpty(batchScript)); continueProcessing = true; if (batchScript.Trim().Length <= 0) { result |= ScriptExecutionResult.Success; return; } Debug.Assert(currentBatch != null); if (executionState == ExecutionState.Cancelling) { result = ScriptExecutionResult.Cancel; } else { currentBatch.Reset(); currentBatch.Text = batchScript; currentBatch.TextSpan = textSpan; currentBatch.BatchIndex = currentBatchIndex; currentBatch.ExpectedExecutionCount = numBatchExecutionTimes; currentBatchIndex++; if (conditions != null) { currentBatch.IsSuppressProviderMessageHeaders = conditions.IsSuppressProviderMessageHeaders; // TODO this is associated with Dacfx specific situations, so uncomment if need be //currentBatch.IsScriptExecutionTracked = conditions.IsScriptExecutionTracked; if (conditions.IsScriptExecutionTracked) { currentBatch.ScriptTrackingId = scriptTrackingId++; } } //ExecutingBatch state means currentBatch is valid to use from another thread to Cancel executionState = ExecutionState.ExecutingBatch; } ScriptExecutionResult batchResult = ScriptExecutionResult.Failure; if (result != ScriptExecutionResult.Cancel) { bool isExecutionDiscarded = false; try { RaiseBatchParserExecutionStarted(currentBatch, textSpan); if (!isLocalParse) { batchResult = DoBatchExecution(currentBatch); } else { batchResult = ScriptExecutionResult.Success; } } finally { isExecutionDiscarded = (executionState == ExecutionState.Discarded); if (executionState == ExecutionState.Cancelling || isExecutionDiscarded) { batchResult = ScriptExecutionResult.Cancel; } else { executionState = ExecutionState.Executing; } } if (!isExecutionDiscarded) { RaiseBatchParserExecutionFinished(currentBatch, batchResult); } } else { batchResult = ScriptExecutionResult.Cancel; } //if we're in Cancel or Halt state, do some special actions if (batchResult == ScriptExecutionResult.Cancel || batchResult == ScriptExecutionResult.Halted) { result = batchResult; continueProcessing = false; return; } else { result |= batchResult; } } /// /// Executes the script by calling ManagedBatchParser.Parse() /// /// The parser will in turn call to the ProcessBatch() which is the /// one starting the execution process /// /// private void DoScriptExecution(bool isBatchParser) { ConfigureBatch(); if (isBatchParser) { ConfigureBatchParser(); try { commandParser.Parse(); } catch (BatchParserException ex) { if (ex.ErrorCode != ErrorCode.Aborted) { result = ScriptExecutionResult.Failure; string info = ex.Text; RaiseScriptError(string.Format(CultureInfo.CurrentCulture, SR.EE_ScriptError_ParsingSyntax, info), ScriptMessageType.FatalError); } } catch (Exception ex) { Logger.Write(TraceEventType.Warning, "Exception Caught in ExecutionEngine.DoScriptExecution(bool): " + ex.ToString()); throw; } } else { ExecuteBatchInternal(script, /* num */ 1, /* lineNumber */ 0); } } /// /// Executes the script (on a separated thread) /// private void DoExecute(bool isBatchParser) { //we should not be in the middle of execution here if (executionState == ExecutionState.Executing || executionState == ExecutionState.ExecutingBatch) { throw new InvalidOperationException(SR.EE_ExecutionNotYetCompleteError); } executionState = ExecutionState.Initial; result = ScriptExecutionResult.Failure; currentBatchIndex = 0; currentBatch.ExecutionTimeout = executionTimeout; expectedShowPlan = ShowPlanType.None; if (!isLocalParse) { errorAction = conditions.IsHaltOnError ? OnErrorAction.Exit : OnErrorAction.Ignore; CreatePrePostConditionBatches(); } ConfigureBatchEventHandlers(currentBatch, batchEventHandlers, true); // do we have a cancel request already? lock (stateSyncLock) { if (executionState == ExecutionState.Cancelling) { RaiseScriptExecutionFinished(ScriptExecutionResult.Cancel); return; } Debug.Assert(executionState == ExecutionState.Initial); executionState = ExecutionState.Executing; } if ((result = ExecutePrePostConditionBatches(preConditionBatches)) == ScriptExecutionResult.Success) { DoScriptExecution(isBatchParser); } if (!CheckForDiscardedConnection()) { if (!isLocalParse) { if (conditions.IsTransactionWrapped && !conditions.IsParseOnly) { if (result == ScriptExecutionResult.Success) { postConditionBatches.Add(new Batch(ExecutionEngineConditions.CommitTransactionStatement, false, executionTimeout)); } else { postConditionBatches.Add(new Batch(ExecutionEngineConditions.RollbackTransactionStatement, false, executionTimeout)); } } // no need to update the result value as it has been updated by the DoScriptExecution() ExecutePrePostConditionBatches(postConditionBatches); } //fire an event that we're done with execution of all batches if (result == ScriptExecutionResult.Halted) //remap into failure { result = ScriptExecutionResult.Failure; } RaiseScriptExecutionFinished(result); } } /// /// Cancels the current batch being executed /// /// /// This method is meant to be called from a separate thread /// in combination with the Cancel method() /// public void CancelCurrentBatch() { ExecutionState state; lock (stateSyncLock) { state = executionState; executionState = ExecutionState.Cancelling; if (state == ExecutionState.ExecutingBatch) { Debug.Assert(currentBatch != null); if (currentBatch != null) { currentBatch.Cancel(); } } } } private void Discard() { Debug.WriteLine("ExecutionEngine.Cancel(): Thread didn't cancel"); ConfigureBatchEventHandlers(currentBatch, batchEventHandlers, false); lock (stateSyncLock) { executionState = ExecutionState.Discarded; } } /// /// Gets the Batch Separator statement /// private string BatchSeparator { get { if (conditions != null) { return conditions.BatchSeparator; } else { return ExecutionEngineConditions.BatchSeparatorStatement; } } } /// /// Create a set of batches to be executed before and after the script is executed /// /// /// This is the way some server side settings can be set. Additionally, it supports /// a way to wrap the script execution within a transaction block /// private void CreatePrePostConditionBatches() { StringBuilder scriptPreBatches = new StringBuilder(); StringBuilder scriptPostBatches = new StringBuilder(); int serverVersion = 8; if (connection != null && connection.State == ConnectionState.Open) { serverVersion = new Version(ReliableConnectionHelper.ReadServerVersion(connection)).Major; } ConfigurePrePostConditionBatches(preConditionBatches); ConfigurePrePostConditionBatches(postConditionBatches); if (conditions.IsNoExec) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.NoExecStatement(false)); } if (conditions.IsStatisticsIO) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsIOStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsIOStatement(false)); } if (conditions.IsStatisticsTime) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsTimeStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsTimeStatement(false)); } if (conditions.IsEstimatedShowPlan) { if (serverVersion >= 9) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ShowPlanXmlStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ShowPlanXmlStatement(false)); expectedShowPlan = ShowPlanType.EstimatedXmlShowPlan; } else { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ShowPlanAllStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ShowPlanAllStatement(false)); expectedShowPlan = ShowPlanType.EstimatedExecutionShowPlan; } } else if (conditions.IsActualShowPlan) { if (serverVersion >= 9) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsXmlStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsXmlStatement(false)); expectedShowPlan = ShowPlanType.ActualXmlShowPlan; } else { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsProfileStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.StatisticsProfileStatement(false)); expectedShowPlan = ShowPlanType.ActualExecutionShowPlan; } } if (conditions.IsTransactionWrapped) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.BeginTransactionStatement); // issuing a Rollback or a Commit will depend on the script execution result } if (conditions.IsParseOnly) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ParseOnlyStatement(true)); scriptPostBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.ParseOnlyStatement(false)); } if (conditions.IsNoExec) { scriptPreBatches.AppendFormat(CultureInfo.InvariantCulture, "{0} ", ExecutionEngineConditions.NoExecStatement(true)); } if (conditions.IsShowPlanText && !conditions.IsEstimatedShowPlan && !conditions.IsActualShowPlan) { // SET SHOWPLAN_TEXT cannot be used with other statements in the batch preConditionBatches.Insert(0, new Batch( string.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(true)), false, executionTimeout)); postConditionBatches.Insert(0, new Batch( string.Format(CultureInfo.CurrentCulture, "{0} ", ExecutionEngineConditions.ShowPlanTextStatement(false)), false, executionTimeout)); } string preBatches = scriptPreBatches.ToString().Trim(); string postBatches = scriptPostBatches.ToString().Trim(); if (scriptPreBatches.Length > 0) { preConditionBatches.Add(new Batch(preBatches, false, executionTimeout)); } if (scriptPostBatches.Length > 0) { postConditionBatches.Add(new Batch(postBatches, false, executionTimeout)); } } /// /// Executes a list of batches related to the Pre and Post scripts /// /// private ScriptExecutionResult ExecutePrePostConditionBatches(IList batches) { Validate.IsNotNull(nameof(batches), batches); ScriptExecutionResult result = ScriptExecutionResult.Success; foreach (Batch batch in batches) { try { ConfigureBatchEventHandlers(batch, batchEventHandlers, true); result = batch.Execute(connection, ShowPlanType.None); } catch (SqlException) { result = ScriptExecutionResult.Failure; } finally { ConfigureBatchEventHandlers(batch, batchEventHandlers, false); } if (result != ScriptExecutionResult.Success) { break; } } return result; } /// /// Cleans up any prev /// private static void ConfigurePrePostConditionBatches(IList batches) { Validate.IsNotNull(nameof(batches), batches); batches.Clear(); } /// /// Binds/Unbinds the methods defined in IBatchEventHandlers with the batch events /// /// Batch to be used in the event binding /// object implementing the IBatcgEventHandlers interface /// Binds or Unbinds the evnts private static void ConfigureBatchEventHandlers(Batch batch, IBatchEventsHandler handlers, bool isHookup) { Validate.IsNotNull(nameof(batch), batch); if (isHookup) { Validate.IsNotNull(nameof(handlers), handlers); batch.BatchError += new EventHandler(handlers.OnBatchError); batch.BatchMessage += new EventHandler(handlers.OnBatchMessage); batch.BatchResultSetProcessing += new EventHandler(handlers.OnBatchResultSetProcessing); batch.BatchResultSetFinished += new EventHandler(handlers.OnBatchResultSetFinished); batch.BatchCancelling += new EventHandler(handlers.OnBatchCancelling); } else { if (handlers != null) { batch.BatchError -= new EventHandler(handlers.OnBatchError); batch.BatchMessage -= new EventHandler(handlers.OnBatchMessage); batch.BatchResultSetProcessing -= new EventHandler(handlers.OnBatchResultSetProcessing); batch.BatchResultSetFinished -= new EventHandler(handlers.OnBatchResultSetFinished); batch.BatchCancelling -= new EventHandler(handlers.OnBatchCancelling); } } } /// /// If a discarded state is found, we will close the connection /// /// /// The discarded state is possible only on a synch Cancel request /// /// /// True if this is discarded connection /// private bool CheckForDiscardedConnection() { bool isDiscarded = false; lock (stateSyncLock) { isDiscarded = (executionState == ExecutionState.Discarded); } if (isDiscarded) { Debug.WriteLine("ExecutionEngine.CheckForDiscardedConnection"); ResetScript(); CloseConnection(connection); return true; } return false; } #endregion #region Private SqlCmd related methods /// /// Called when parser is about to halt the execution /// private void OnHaltParser() { result = ScriptExecutionResult.Halted; } /// /// Changed when parser changed the error action type /// /// private void OnErrorActionChanged(OnErrorAction ea) { errorAction = ea; } /// /// Called when parser requests a new connection /// /// private void OnConnectionChanged(SqlConnectionStringBuilder connectionStringBuilder) { // make sure that we disconnect any previous SqlCmd connection DisconnectSqlCmdInternal(); // create a new SqlCmd connection SqlConnection connection = ConnectSqlCmdInternal(connectionStringBuilder); if (connection != null) { isSqlCmdConnection = true; this.connection = connection; CreatePrePostConditionBatches(); result = ExecutePrePostConditionBatches(preConditionBatches); } } /// /// Connects when :connect is identified within the script /// /// /// private static SqlConnection ConnectSqlCmdInternal(SqlConnectionStringBuilder connectionStringBuilder) { Validate.IsNotNull(nameof(connectionStringBuilder), connectionStringBuilder); SqlConnection connection = null; try { connection = new SqlConnection(connectionStringBuilder.ConnectionString); connection.Open(); } catch (SqlException ex) { Logger.Write(TraceEventType.Warning, "Exception Caught in ExecutionEngine.ConnectSqlCmdInternal(SqlConnectionStringBuilder): " + ex.ToString()); throw; } return connection; } /// /// Disconnects a sqlcmd connection /// private void DisconnectSqlCmdInternal() { if (isSqlCmdConnection) { RaiseBatchMessage(string.Format(CultureInfo.CurrentCulture, "Disconnection from server {0}", ReliableConnectionHelper.GetServerName(connection))); CloseConnection(connection); } } #endregion #region Private methods /// /// Closes a connection /// /// static private void CloseConnection(IDbConnection connection) { if (connection != null && connection.State == ConnectionState.Open) { try { connection.Close(); } catch (SqlException ex) { Logger.Write(TraceEventType.Warning, "Exception Caught in ExecutionEngine.CloseConnection(SqlConnection): " + ex.ToString()); } } } /// /// Setups the script execution /// /// private void ExecuteInternal(ScriptExecutionArgs scriptExecutionArgs, bool isBatchParser) { Validate.IsNotNull(nameof(scriptExecutionArgs), scriptExecutionArgs); Validate.IsNotNullOrEmptyString(nameof(scriptExecutionArgs.Script), scriptExecutionArgs.Script); Validate.IsNotNull(nameof(scriptExecutionArgs.ReliableConnection), scriptExecutionArgs.ReliableConnection); Validate.IsNotNull(nameof(scriptExecutionArgs.Conditions), scriptExecutionArgs.Conditions); Validate.IsNotNull(nameof(scriptExecutionArgs.BatchEventHandlers), scriptExecutionArgs.BatchEventHandlers); Debug.Assert(scriptExecutionArgs.TimeOut >= 0); executionTimeout = scriptExecutionArgs.TimeOut < 0 ? 0 : scriptExecutionArgs.TimeOut; connection = scriptExecutionArgs.ReliableConnection; conditions = new ExecutionEngineConditions(scriptExecutionArgs.Conditions); script = scriptExecutionArgs.Script; isSqlCmdConnection = false; batchEventHandlers = scriptExecutionArgs.BatchEventHandlers; startingLine = scriptExecutionArgs.StartingLine; internalVariables = scriptExecutionArgs.Variables; DoExecute(isBatchParser); } #endregion #region Public Events /// /// This event gets fired when execution of one batch is completed /// public event EventHandler BatchParserExecutionFinished = null; /// /// This event gets fired when execution of a batch is about to start /// public event EventHandler BatchParserExecutionStart = null; /// /// This event gets fired when when there's an error/warnings from the scripting engine /// public event EventHandler BatchParserExecutionError = null; /// /// This event gets fired when the script execution is completed /// public event EventHandler ScriptExecutionFinished = null; #endregion #region Public members /// /// Executes the script /// /// Script to be executed public void ExecuteScript(object scriptArgs) { ExecuteInternal(scriptArgs as ScriptExecutionArgs, /* isBatchParser */ true); } /// /// Executes a given batch /// /// /// public void ExecuteBatch(ScriptExecutionArgs scriptExecutionArgs) { ExecuteInternal(scriptExecutionArgs, /* isBatchParser */ false); } /// /// Parses the script locally /// /// script to parse /// batch handler /// /// The batch parser functionality is used in this case /// public void ParseScript(string script, IBatchEventsHandler batchEventsHandler) { Validate.IsNotNull(nameof(script), script); Validate.IsNotNull(nameof(batchEventsHandler), batchEventsHandler); this.script = script; batchEventHandlers = batchEventsHandler; isLocalParse = true; DoExecute(/* isBatchParser */ true); } /// /// Close the current connection /// /// public void Close(bool isCloseConnection) { Close(isCloseConnection, /* isDiscard */ false); } /// /// Close/Discard the current connection /// /// /// public void Close(bool isCloseConnection, bool isDiscard) { Close(isCloseConnection, isDiscard, /* isFinishExecution */ false); } /// /// Close/Discard the current connection /// /// true if connection has to be closed /// true if connection has to be discarded /// Raises the script execution finish event public void Close(bool isCloseConnection, bool isDiscard, bool isFinishExecution) { if (isFinishExecution) { RaiseScriptExecutionFinished(ScriptExecutionResult.Cancel); } if (isDiscard) { Discard(); } else { if (isCloseConnection) { CloseConnection(connection); } } } /// /// Performs application-defined tasks associated with freeing, releasing, or // resetting unmanaged resources. /// public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if (disposing) { //Debug.WriteLine("ExecutionEngine.Dispose"); if (commandParser != null) { commandParser.Dispose(); commandParser = null; } ResetScript(); stateSyncLock = null; currentBatch = null; } } #endregion } }