/// <summary>
        /// Adds input batch context data to the exception data.
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="batch">The batch.</param>
        /// <exception cref="ArgumentNullException">batch is null.</exception>
        public static void AddContextData(this SqlException ex, SqlBatch batch)
        {
            if (batch == null)
            {
                throw new ArgumentNullException(nameof(batch));
            }

            var primaryException = ex.Errors[0];
            var isProcedureError = !string.IsNullOrEmpty(primaryException.Procedure);

            ex.Data.Add(Server, string.IsNullOrEmpty(ex.Server) ? "Unknown" : ex.Server);
            ex.Data.Add(BatchBeginLineNumber, batch.BatchBeginLineNumber);
            ex.Data.Add(BatchSource, batch.Source);
            ex.Data.Add(IsProcedureError, isProcedureError);
            ex.Data.Add(SourceErrorLine, primaryException.LineNumber + batch.BatchBeginLineNumber - 1);

            if (isProcedureError)
            {
                ex.Data.Add(ErrorStatement, $"EXEC {ex.Procedure} ... (in input batch)");
            }
            else
            {
                var sb    = new StringBuilder();
                var lines = batch.Sql.Split(new[] { Environment.NewLine }, StringSplitOptions.None);

                for (var i = Math.Max(0, ex.LineNumber - 1);
                     i < Math.Min(lines.Length, ex.LineNumber + 10);
                     ++i)
                {
                    sb.AppendLine(lines[i]);
                }

                ex.Data.Add(ErrorStatement, sb.ToString());
            }

            // This entry just to signify that context data was successfully added.
            ex.Data.Add(HasContextData, true);
        }
Example #2
0
        /// <summary>
        /// Parses this instance.
        /// </summary>
        /// <exception cref="ParserException">Syntax error or unrecognized command directive
        /// or
        /// or</exception>
        public void Parse()
        {
            var sw = new Stopwatch();

            sw.Start();

            try
            {
                if (this.runConfiguration.OutputFile != null)
                {
                    this.CommandExecuter.Out(OutputDestination.File, this.runConfiguration.OutputFile);
                }

                this.InputSourceChanged += this.CommandExecuter.OnInputSourceChanged;

                this.CommandExecuter.ConnectWithConnectionString(this.runConfiguration.ConnectionString);

                this.SetInputSource(this.runConfiguration.InitialBatchSource);
                var tokenizer = new Tokenizer();

                while (this.sourceStack.Count > 0)
                {
                    this.Source = this.sourceStack.Peek();

                    if (this.currentBatch == null)
                    {
                        this.currentBatch = new SqlBatch();
                    }

                    var line = this.Source.GetNextLine();

                    if (line == null)
                    {
                        // End of input source
                        if (tokenizer.State != TokenizerState.None)
                        {
                            // Incomplete parse
                            throw new ParserException(tokenizer.State, this.Source);
                        }

                        this.sourceStack.Pop();
                        continue;
                    }

                    tokenizer.AddLine(line);
                    Token token;

                    var isStartOfLine = true;

                    while ((token = tokenizer.GetNextToken()) != null)
                    {
                        if (tokenizer.State == TokenizerState.None)
                        {
                            // Not in a comment or a multi-line string literal
                            if (isStartOfLine)
                            {
                                // Command matching only when we are at the beginning of a line
                                var commandMatched = false;

                                foreach (var commandMatcher in this.commandMatchers)
                                {
                                    if (!commandMatcher.IsMatch(token.TokenValue))
                                    {
                                        continue;
                                    }

                                    switch (commandMatcher)
                                    {
                                    case GoCommand go:

                                        if (string.IsNullOrWhiteSpace(this.currentBatch.Sql))
                                        {
                                            break;
                                        }

                                        try
                                        {
                                            // Increment first, so a failed batch is counted
                                            ++this.BatchCount;
                                            this.CommandExecuter.ProcessBatch(this.currentBatch, go.ExecutionCount);
                                        }
                                        finally
                                        {
                                            this.previousBatch = this.currentBatch;
                                            this.currentBatch  = new SqlBatch();
                                        }

                                        break;

                                    case SetvarCommand setvar:

                                        if (setvar.VarValue == null)
                                        {
                                            this.VariableResolver.DeleteVariable(setvar.VarValue);
                                        }
                                        else
                                        {
                                            this.VariableResolver.SetVariable(setvar.VarName, setvar.VarValue);
                                        }

                                        break;

                                    case IncludeCommand include:

                                        string resolvedPath;

                                        if (Path.IsPathRooted(include.Filename))
                                        {
                                            resolvedPath = include.Filename;
                                        }
                                        else if (!include.Filename.Contains(Path.DirectorySeparatorChar))
                                        {
                                            // Assume the included file is in the same location as the current batch source, or current directory if no source.
                                            resolvedPath = Path.Combine(
                                                File.Exists(this.Source.Filename)
                                                        ? Path.GetDirectoryName(this.Source.Filename)
                                                        : this.currentDirectoryResolver.GetCurrentDirectory(),
                                                include.Filename);
                                        }
                                        else
                                        {
                                            resolvedPath = include.Filename;
                                        }

                                        this.SetInputSource(this.CommandExecuter.IncludeFileName(resolvedPath));

                                        break;

                                    case ConnectCommand connect:

                                        connect.ResolveConnectionParameters(this.VariableResolver);

                                        if (connect.Server != null)
                                        {
                                            // Execute whatever we have so far on the current connection before reconnecting
                                            try
                                            {
                                                this.CommandExecuter.ProcessBatch(this.currentBatch, 1);
                                            }
                                            finally
                                            {
                                                this.previousBatch = this.currentBatch;
                                                this.currentBatch  = new SqlBatch();
                                                ++this.BatchCount;
                                            }

                                            this.CommandExecuter.Connect(
                                                connect.Timeout,
                                                connect.Server,
                                                connect.Username,
                                                connect.Password);
                                        }

                                        break;

                                    case EdCommand _:

                                        if (!this.disableInteractiveCommands && Environment.UserInteractive)
                                        {
                                            var currentBatchStr = this.currentBatch.Sql;
                                            var batchToEdit     =
                                                string.IsNullOrEmpty(currentBatchStr)
                                                        ? this.previousBatch.Sql
                                                        : currentBatchStr;

                                            if (!string.IsNullOrWhiteSpace(batchToEdit))
                                            {
                                                var editedBatch = this.CommandExecuter.Ed(batchToEdit);

                                                if (editedBatch != null)
                                                {
                                                    this.SetInputSource(editedBatch);
                                                    this.currentBatch = new SqlBatch();
                                                }
                                            }
                                        }

                                        break;

                                    case ExitCommand exit:

                                        if (exit.ExitImmediately)
                                        {
                                            return;
                                        }

                                        this.CommandExecuter.Exit(this.currentBatch, exit.ExitBatch);
                                        return;

                                    case ErrorCommand error:

                                        this.CommandExecuter.Error(
                                            error.OutputDestination,
                                            FileParameterCommand.GetNodeFilepath(this.nodeNumber, error.Filename));
                                        break;

                                    case OutCommand @out:

                                        this.CommandExecuter.Out(
                                            @out.OutputDestination,
                                            FileParameterCommand.GetNodeFilepath(this.nodeNumber, @out.Filename));
                                        break;

                                    // ReSharper disable once UnusedVariable
                                    case ServerListCommand serverList:

                                        if (!this.disableInteractiveCommands)
                                        {
                                            this.CommandExecuter.ServerList();
                                        }

                                        break;

                                    // ReSharper disable once UnusedVariable
                                    case HelpCommand help:

                                        if (!this.disableInteractiveCommands)
                                        {
                                            this.CommandExecuter.Help();
                                        }

                                        break;

                                    case ResetCommand _:

                                        if (!this.disableInteractiveCommands)
                                        {
                                            this.CommandExecuter.Reset();
                                            this.currentBatch.Clear();
                                        }

                                        break;

                                    case ListCommand _:

                                        if (!this.disableInteractiveCommands)
                                        {
                                            this.CommandExecuter.List(this.currentBatch.Sql);
                                        }

                                        break;

                                    case ListVarCommand _:

                                        if (!this.disableInteractiveCommands)
                                        {
                                            this.CommandExecuter.ListVar(this.VariableResolver.Variables);
                                        }

                                        break;

                                    case ShellCommand shell:

                                        this.CommandExecuter.ExecuteShellCommand(shell.Command);
                                        break;

                                    case QuitCommand _:

                                        this.CommandExecuter.Quit();

                                        // Stop parsing
                                        return;

                                    case OnErrorCommand onError:

                                        this.CommandExecuter.OnError(onError.ErrorAction);
                                        break;

                                    case InvalidCommand _:

                                        throw new ParserException(
                                                  "Syntax error or unrecognized command directive",
                                                  this.Source);
                                    }

                                    commandMatched = true;
                                    break;
                                }

                                if (commandMatched)
                                {
                                    // Jump to next line, appending a blank line where we removed the command
                                    break;
                                }
                            }

                            this.AppendToken(token);
                        }
                        else
                        {
                            // In a comment, just append to batch buffer
                            this.AppendToken(token);
                        }

                        isStartOfLine = false;
                    }
                }

                if (tokenizer.State != TokenizerState.None)
                {
                    // Incomplete parse
                    throw ParserException.CreateInvalidTokenizerStateException(tokenizer.State, this.Source);
                }

                // If we have anything left, it's the last batch
                if (!string.IsNullOrWhiteSpace(this.currentBatch.Sql))
                {
                    // Increment first, so a failed batch is counted
                    ++this.BatchCount;
                    this.CommandExecuter.ProcessBatch(this.currentBatch, 1);
                }
            }
            catch (ParserException)
            {
                throw;
            }
            catch (SqlException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new ParserException(ex.Message, ex, this.Source);
            }
            finally
            {
                // Output statistics
                sw.Stop();

                var batches = this.BatchCount == 1 ? "batch" : "batches";
                var errors  = this.CommandExecuter.ErrorCount == 1 ? "error" : "errors";

                this.CommandExecuter.WriteStdoutMessage(
                    string.Join(
                        Environment.NewLine,
                        Environment.NewLine,
                        $"{this.BatchCount} {batches} processed in {sw.Elapsed.Minutes} min, {sw.Elapsed.Seconds}.{sw.Elapsed.Milliseconds:D3} sec.",
                        $"{this.CommandExecuter.ErrorCount} SQL {errors} in execution."));
            }
        }