/// <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) }); } }
/// <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.")); } }