/// <summary>
        /// Initializes a new instance of the <see cref="SqlExecuteImpl"/> class.
        /// </summary>
        /// <param name="arguments">The args.</param>
        public SqlExecuteImpl(ISqlExecuteArguments arguments)
        {
            this.arguments = arguments;

            if (this.arguments.ParseOnly)
            {
                this.arguments.OutputMessage?.Invoke(
                    this,
                    new OutputMessageEventArgs(0, "DRY RUN mode - No SQL will be executed.", OutputDestination.StdOut));
            }

            this.runList = new List <RunConfiguration>();

            var inputFileCount = arguments.InputFile?.Length ?? 0;

            for (var i = 0; i < Math.Max(arguments.ConnectionString.Length, inputFileCount); ++i)
            {
                var r = CreateVariableResolver(arguments);

                this.runList.Add(
                    new RunConfiguration
                {
                    NodeNumber       = i + 1,
                    CommandExecuter  = CreateCommandExecuter(i + 1, arguments, r),
                    ConnectionString =
                        arguments.ConnectionString.Length == 1
                                    ? arguments.ConnectionString[0]
                                    : arguments.ConnectionString[i],
                    InitialBatchSource =
                        this.GetInitialBatchSource(Math.Min(i, inputFileCount - 1)),
                    VariableResolver = r,
                    OutputFile       =
                        string.IsNullOrEmpty(arguments.OutputFile)
                                    ? null
                                    : new OutputFileProperties(
                            FileParameterCommand.GetNodeFilepath(
                                arguments.RunParallel ? i + 1 : 0,
                                arguments.OutputFile),
                            i == 0 || arguments.RunParallel ? FileMode.Create : FileMode.Append)
                });
            }
        }
Exemple #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."));
            }
        }