/// <summary>
        /// Extracts a tag matching the specified regular expression from the script
        /// </summary>
        /// <param name="scriptContents">The text of the script</param>
        /// <param name="regexFormats">List of regular expression formats</param>
        /// <returns>The tag string found, empty string if not found</returns>
        internal static string InferScriptTagFromFileContents(string scriptContents, List <string> regexFormats)
        {
            if (regexFormats == null || regexFormats.Count == 0)
            {
                return(string.Empty);
            }

            Match tmpLastMatch = null;

            foreach (string reg in regexFormats)
            {
                Regex           tmpRegex        = new Regex(reg, RegexOptions.IgnoreCase);
                MatchCollection tmpFileContents = tmpRegex.Matches(scriptContents);
                foreach (Match contentMatch in tmpFileContents)
                {
                    if (ScriptHandlingHelper.IsInLargeCommentHeader(scriptContents, contentMatch.Index))
                    {
                        if (tmpLastMatch == null || contentMatch.Index > tmpLastMatch.Index)
                        {
                            tmpLastMatch = contentMatch;
                        }
                    }
                }
            }
            if (tmpLastMatch == null)
            {
                return(string.Empty);
            }
            else
            {
                return(CleanTag(tmpLastMatch.Value));
            }
        }
예제 #2
0
        /// <summary>
        /// Add WITH (NOLOCK) directive to all FROM and JOIN selects in a SQL query
        /// </summary>
        /// <param name="rawScript">Raw incomming script</param>
        /// <param name="commentBlockMatches"></param>
        /// <param name="tablesMissingNoLock"></param>
        /// <returns>Script updated with the WITH (NOLOCK) directive</returns>
        public static string ProcessNoLockOptimization(string rawScript, List <Match> commentBlockMatches, out List <List <string> > tablesMissingNoLock)
        {
            tablesMissingNoLock = new List <List <string> >();
            try
            {
                if (commentBlockMatches == null)
                {
                    commentBlockMatches = ScriptHandlingHelper.GetScriptCommentBlocks(rawScript);
                }

                StringBuilder sb     = new StringBuilder();
                string        noLock = " WITH (NOLOCK) ";
                string        subStr;
                int           lengthToWhere;
                Match         current;
                Match         next;
                Match         previous             = null;
                Regex         regTokens            = new Regex(@"(\bINNER JOIN\b)|(\bOUTER JOIN\b) |(\bFROM\b)|(\bWHERE\b)|(\bON\b)|(WITH \(NOLOCK\))|(WITH \(READPAST\))|(\bLEFT JOIN\b)|(\bRIGHT JOIN\b)|(\bINTO\b)|(\bJOIN\b)|(\bGROUP BY\b)|(\bDELETE FROM\b)|(\bUPDATE\b)|(\bset\b)|(\bINSERT INTO\b)|(\bVALUES\b)|(\))|(\binserted\b)|(\bdeleted\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regSelects           = new Regex(@"(\bINNER JOIN\b)|(\bOUTER JOIN\b) |(\bFROM\b)|(\bLEFT JOIN\b)|(\bRIGHT JOIN\b)|(\bJOIN\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regWheres            = new Regex(@"(\bWHERE\b)|(\bON\b)|(\bINNER JOIN\b)|(\bOUTER JOIN\b)|(\bLEFT JOIN\b)|(\bRIGHT JOIN\b)|(\bJOIN\b)|(\))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regJoin              = new Regex(@"(\bINNER JOIN\b)|(\bOUTER JOIN\b)|(\bJOIN\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regFrom              = new Regex(@"(\bFROM\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regFromWithTableName = new Regex(@"(\bFROM\b\s*.*\s*)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regEndWhiteSpace     = new Regex(@"(\s*$)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex         regNewLine           = new Regex(@"(\r)|(\n)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                //Regex regNoLock = new Regex(@"(WITH \(NOLOCK\))|(WITH \(READPAST\))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regInto          = new Regex(@"(\bINTO\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regDelete        = new Regex(@"(\bDELETE FROM\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regUpdate        = new Regex(@"(\bUPDATE\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regSet           = new Regex(@"(\bSET\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regInsertInto    = new Regex(@"(\bINSERT INTO\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regValues        = new Regex(@"(\bVALUES\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                Regex regFindFunction  = new Regex(@"(\b\S+\(.*\)\s*)", RegexOptions.Compiled);
                Regex regTriggerTables = new Regex(@"(\binserted\b)|(\bdeleted\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

                //Regex regIgnoreObjects = new Regex(@"(FROM sys\.objects)|(FROM INFORMATION_SCHEMA)|(FROM sysobjects)|(FROM sysindexes)|(FROM sys\.indexes)|(FROM sys\.triggers)|(FROM dbo\.sysusers)|(FROM syscolumns)|(FROM sys\.columns)|(FROM sys\.stats)|(FROM sys\.foreign_keys)", RegexOptions.IgnoreCase);
                Regex           regIgnoreObjects = new Regex(@"(FROM\s+sys\.\w+)|(FROM\s+dbo\.sys\w+)|(FROM INFORMATION_SCHEMA)|(FROM sysobjects)|(FROM sysindexes)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
                MatchCollection coll             = regTokens.Matches(rawScript);
                int             textIndex        = 0;
                for (int i = 0; i < coll.Count; i++)
                {
                    current = coll[i];

                    if (i == coll.Count - 1 && !regFrom.Match(current.Value).Success)
                    {
                        break;
                    }
                    else if (i == coll.Count - 1) // the last match is an unpaired FROM
                    {
                        next = current;           //give it a fake value
                    }
                    else
                    {
                        next = coll[i + 1];
                    }

                    //Ignore DELETE FROM .. WHERE.
                    if (regDelete.Match(current.Value).Success&& regWheres.Match(next.Value).Success)
                    {
                        continue;
                    }

                    //Ignore UPDATE .. SET.
                    if (regUpdate.Match(current.Value).Success&& regSet.Match(next.Value).Success)
                    {
                        continue;
                    }

                    //Ignore INSERT INTO .. VALUES.
                    if (regInsertInto.Match(current.Value).Success&& regValues.Match(next.Value).Success)
                    {
                        continue;
                    }
                    //Ignore select on Trigger tables "FROM inserted" and "FROM deleted"
                    if (regFrom.Match(current.Value).Success&& regTriggerTables.Match(next.Value).Success)
                    {
                        continue;
                    }



                    if (regSelects.Match(current.Value).Success&& regWheres.Match(next.Value).Success&& !regNoLock.Match(next.Value).Success)   // we have found our FROM .. WHERE or INNER JOIN ... ON limits
                    {
                        lengthToWhere = next.Index - textIndex;


                        //Have we found a FROM value that selects from one of the ignore objects??
                        //Are we selecting from a table variable or temp table??
                        //Are we selecting from a function?
                        //Is the match in a comment?
                        Match  ignore       = regIgnoreObjects.Match(rawScript, current.Index);
                        string strCheck     = rawScript.Substring(current.Index + current.Length).Trim();
                        string strFuncCheck = rawScript.Substring(current.Index, next.Index - current.Index);
                        if (next.Value == ")")
                        {
                            strFuncCheck += ")";
                        }
                        if ((ignore.Success && ignore.Index == current.Index) || strCheck.StartsWith("@") ||
                            strCheck.StartsWith("#") || regFindFunction.Match(strFuncCheck).Success || ScriptHandlingHelper.IsInComment(current.Index, commentBlockMatches))
                        {
                            if (ignore.Success && ignore.Index == current.Index)
                            {
                                lengthToWhere = next.Index - ignore.Index + 1;
                            }
                            else
                            {
                                lengthToWhere = next.Index - textIndex;
                            }

                            if (textIndex + lengthToWhere > rawScript.Length || lengthToWhere < 0)
                            {
                                continue;
                            }

                            subStr = rawScript.Substring(textIndex, lengthToWhere);
                            sb.Append(subStr);
                            textIndex += lengthToWhere;
                            continue;
                        }

                        //since astetically, we want the NOLOCK to be on the same line as the FROM table name, need to check for a line break...
                        if (lengthToWhere < 0)
                        {
                            continue;
                        }

                        subStr = rawScript.Substring(textIndex, lengthToWhere);
                        if (regEndWhiteSpace.Match(subStr).Success&&
                            regNewLine.Match(regEndWhiteSpace.Match(subStr).Value).Success)
                        {
                            tablesMissingNoLock.Add(GetTableName(subStr));
                            string whiteSpace = regEndWhiteSpace.Match(subStr).Value;
                            sb.Append(subStr.TrimEnd() + noLock + whiteSpace + next.Value);
                        }
                        else
                        {
                            tablesMissingNoLock.Add(GetTableName(subStr));
                            sb.Append(subStr.TrimEnd() + noLock + next.Value);
                        }
                        if (!regJoin.Match(next.Value).Success) //if the next token is a JOIN, don't skip it.
                        {
                            i++;
                        }

                        textIndex = next.Index + next.Length;
                    }
                    else if (regFrom.Match(current.Value).Success&& regInto.Match(next.Value).Success)  // looks like a cursor... FROM xyx INTO
                    {
                        int len = next.Index - textIndex;
                        if (len < 0 || textIndex + len > rawScript.Length)
                        {
                            continue;
                        }

                        subStr = rawScript.Substring(textIndex, len);
                        sb.Append(subStr + next.Value);
                        textIndex = next.Index + next.Length;
                    }
                    else if (regFrom.Match(current.Value).Success&& !regNoLock.Match(next.Value).Success)  //handle the FROM that doesn't have a following WHERE or INNER or OUTER JOINS
                    {
                        int len;
                        if (current.Index > textIndex)
                        {
                            //    if (ScriptHandlingHelper.IsInComment(current.Index, commentBlockMatches))
                            //        len = current.Index + current.Length - 4; //current.Index - textIndex;
                            //    else
                            len = current.Index - textIndex + 4;

                            if (len < 0 || textIndex + len > rawScript.Length)
                            {
                                continue;
                            }

                            sb.Append(rawScript.Substring(textIndex, len));
                        }

                        // if(ScriptHandlingHelper.IsInComment(current.Index, commentBlockMatches))
                        //     subStr = regFromWithTableName.Match(rawScript, current.Index).Value;
                        // else
                        subStr = regFromWithTableName.Match(rawScript, current.Index).Value.Substring(4);

                        len = next.Index - current.Index;
                        if (len < 0 || current.Index + len > rawScript.Length)
                        {
                            continue;
                        }

                        string strFuncCheck = rawScript.Substring(current.Index, len);
                        if (next.Value == ")")
                        {
                            strFuncCheck += ")";
                        }

                        //selecting against a table variable or temp table?
                        //selecting against a function?
                        //in a comment?
                        if (subStr.Trim().StartsWith("@") || subStr.Trim().StartsWith("#") ||
                            regFindFunction.Match(strFuncCheck).Success || ScriptHandlingHelper.IsInComment(current.Index, commentBlockMatches))
                        {
                            sb.Append(subStr);
                        }
                        else if (previous != null &&
                                 (previous.Value.ToLower().Trim().StartsWith("delete") ||
                                  previous.Value.ToLower().Trim().StartsWith("insert") ||
                                  previous.Value.ToLower().Trim().StartsWith("update"))) //Covers where there is a "delete, insert or update" that is followed by a FROM but no WHERE
                        {
                            sb.Append(subStr);
                        }
                        else
                        {
                            string append;
                            if (regEndWhiteSpace.Match(subStr).Success&& regNewLine.Match(regEndWhiteSpace.Match(subStr).Value).Success)
                            {
                                tablesMissingNoLock.Add(GetTableName(subStr));
                                append = subStr.TrimEnd() + noLock + "\r\n";
                                sb.Append(append);
                            }
                            else
                            {
                                tablesMissingNoLock.Add(GetTableName(subStr));
                                append = subStr.TrimEnd() + noLock;
                                sb.Append(append);
                            }
                        }

                        if (current.Index + subStr.Length + 4 == rawScript.Length) //catch if we're at the end
                        {
                            textIndex = rawScript.Length;
                        }
                        else
                        {
                            //        if (ScriptHandlingHelper.IsInComment(current.Index, commentBlockMatches))
                            //        {
                            //            textIndex = current.Index + subStr.Length + current.Length;
                            //                ;
                            //        }
                            //        else
                            //        {
                            textIndex = current.Index + subStr.Length + 4;
                            //       }

                            //this used to be "1"...changed to 4 and all unit tests still pass!?!
                        }
                    }
                    else //got nothing.. just add the text.
                    {
                        int len = next.Index + next.Length - textIndex;
                        if (len < 0 || textIndex + len > rawScript.Length)
                        {
                            continue;
                        }

                        sb.Append(rawScript.Substring(textIndex, len));
                        textIndex = next.Index + next.Length;
                    }

                    previous = current;
                }
                sb.Append(rawScript.Substring(textIndex, rawScript.Length - textIndex));
                return(sb.ToString());
            }
            catch (Exception exe)
            {
                log.LogError(exe, "Error processing Script Optimization");
                throw;
            }
        }