У меня есть требование провести аудит всех наших хранимых процедур, тысяч из них, и определить, какие из них предназначены только для чтения или для чтения-записи.Мне было интересно, если кто-нибудь знает хороший способ сделать это точно.
Я уже написал свой собственный сценарий, но я получаю точность только ~ 85%.Я запутался в хранимых процедурах, которые действительно доступны только для чтения, но они создают несколько временных таблиц.Для моих целей это только для чтения.Я не могу просто игнорировать их, потому что есть много процедур чтения-записи, работающих с временными таблицами.
[EDIT] Я получил примерно ~ 85% точности, посмотрев на 20Процедуры, которые я знаю, довольно сложны и сравнивают их с результатами, полученными в результате запроса.
Вот запрос, который я сейчас использую:
CREATE TABLE tempdb.dbo.[_tempProcs]
(objectname varchar(150), dbname varchar(150), ROUTINE_DEFINITION varchar(4000))
GO
EXEC sp_MSforeachdb
'USE [?]
DECLARE @dbname VARCHAR(200)
SET @dbname = DB_NAME()
IF 1 = 1 AND ( @dbname NOT IN (''master'',''model'',''msdb'',''tempdb'',''distribution'')
BEGIN
EXEC(''
INSERT INTO tempdb.dbo.[_tempProcs](objectname, dbname, ROUTINE_DEFINITION)
SELECT ROUTINE_NAME AS ObjectName, ''''?'''' AS dbname, ROUTINE_DEFINITION
FROM [?].INFORMATION_SCHEMA.ROUTINES WITH(NOLOCK)
WHERE ROUTINE_DEFINITION LIKE ''''%INSERT [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%UPDATE [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%INTO [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%DELETE [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%CREATE TABLE[^]%''''
OR ROUTINE_DEFINITION LIKE ''''%DROP [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%ALTER [^]%''''
OR ROUTINE_DEFINITION LIKE ''''%TRUNCATE [^]%''''
AND ROUTINE_TYPE=''''PROCEDURE''''
'')
END
'
GO
SELECT * FROM tempdb.dbo.[_tempProcs] WITH(NOLOCK)
Я еще не уточнил его, сейчас я просто хочу сосредоточиться на доступных для записи запросах и посмотреть, смогу ли я его получить.точный.Также еще одна проблема заключается в том, что ROUTINE_DEFINITION дает только первые 4000 символов, поэтому я могу пропустить любые, которые пишут после длины 4000 символов.Я мог бы фактически закончить с комбинацией предложений.Получите список процедур, возвращаемых этим запросом, а затем попробуйте предложение Arrons и посмотрите, смогу ли я отсеять еще больше.Я был бы счастлив с точностью 95%.
Я дам еще один день или около того, чтобы посмотреть, смогу ли я получить какие-либо дальнейшие предложения, но пока большое спасибо.
[ОКОНЧАТЕЛЬНОЕ РЕДАКТИРОВАНИЕ] Хорошо, вот что я в итоге сделал, и похоже, что я получаю точность не менее 95%, может быть выше.Я пытался удовлетворить любой сценарий, который мог придумать.
Я записал хранимые процедуры в файлы и написал приложение ac # winform для анализа файлов и нахождения тех, которые имеют законные записи'к реальной базе данных.
Я рад опубликовать этот код для государственного механизма, который я использовал здесь, но без гарантий.Я под давлением, чтобы доставить, и действительно не было времени, чтобы украсить код, и рефакторинг с хорошими именами переменных и т. Д. И помещать в него хорошие комментарии, у меня было 3 часа, и я просто сжал его,Те, кто заботится и может помочь в будущем, вот он:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace SQLParser
{
public class StateEngine
{
public static class CurrentState
{
public static bool IsInComment;
public static bool IsInCommentBlock;
public static bool IsInInsert;
public static bool IsInUpdate;
public static bool IsInDelete;
public static bool IsInCreate;
public static bool IsInDrop;
public static bool IsInAlter;
public static bool IsInTruncate;
public static bool IsInInto;
}
public class ReturnState
{
public int LineNumber { get; set; }
public bool Value { get; set; }
public string Line { get; set; }
}
private static int _tripLine = 0;
private static string[] _lines;
public ReturnState ParseFile(string fileName)
{
var retVal = false;
_tripLine = 0;
ResetCurrentState();
_lines = File.ReadAllLines(fileName);
for (int i = 0; i < _lines.Length; i++)
{
retVal = ParseLine(_lines[i], i);
//return true the moment we have a valid case
if (retVal)
{
ResetCurrentState();
return new ReturnState() { LineNumber = _tripLine, Value = retVal, Line = _lines[_tripLine] };
}
}
if (CurrentState.IsInInsert ||
CurrentState.IsInDelete ||
CurrentState.IsInUpdate ||
CurrentState.IsInDrop ||
CurrentState.IsInAlter ||
CurrentState.IsInTruncate)
{
retVal = true;
ResetCurrentState();
return new ReturnState() { LineNumber = _tripLine, Value = retVal, Line = _lines[_tripLine] };
}
return new ReturnState() { LineNumber = -1, Value = retVal };
}
private static void ResetCurrentState()
{
CurrentState.IsInAlter = false;
CurrentState.IsInCreate = false;
CurrentState.IsInDelete = false;
CurrentState.IsInDrop = false;
CurrentState.IsInInsert = false;
CurrentState.IsInTruncate = false;
CurrentState.IsInUpdate = false;
CurrentState.IsInInto = false;
CurrentState.IsInComment = false;
CurrentState.IsInCommentBlock = false;
}
private static bool ParseLine(string sqlLine, int lineNo)
{
var retVal = false;
var _currentWord = 0;
var _tripWord = 0;
var _offsetTollerance = 4;
sqlLine = sqlLine.Replace("\t", " ");
//This would have been set in previous line, so reset it
if (CurrentState.IsInComment)
CurrentState.IsInComment = false;
var words = sqlLine.Split(char.Parse(" ")).Where(x => x.Length > 0).ToArray();
for (int i = 0; i < words.Length; i++)
{
if (string.IsNullOrWhiteSpace(words[i]))
continue;
_currentWord += 1;
if (CurrentState.IsInCommentBlock && words[i].EndsWith("*/") || words[i] == "*/") { CurrentState.IsInCommentBlock = false; }
if (words[i].StartsWith("/*")) { CurrentState.IsInCommentBlock = true; }
if (words[i].StartsWith("--") && !CurrentState.IsInCommentBlock) { CurrentState.IsInComment = true; }
if (words[i].Length == 1 && CurrentState.IsInUpdate)
{
//find the alias table name, find 'FROM' and then next word
var tempAlias = words[i];
var tempLine = lineNo;
for (int l = lineNo; l < _lines.Length; l++)
{
var nextWord = "";
var found = false;
var tempWords = _lines[l].Replace("\t", " ").Split(char.Parse(" ")).Where(x => x.Length > 0).ToArray();
for (int m = 0; m < tempWords.Length; m++)
{
if (found) { break; }
if (tempWords[m].ToLower() == tempAlias && tempWords[m - m == 0 ? m : 1].ToLower() != "update")
{
nextWord = m == tempWords.Length - 1 ? "" : tempWords[m + 1].ToString();
var prevWord = m == 0 ? "" : tempWords[m - 1].ToString();
var testWord = "";
if (nextWord.ToLower() == "on" || nextWord == "")
{
testWord = prevWord;
}
if (prevWord.ToLower() == "from")
{
testWord = nextWord;
}
found = true;
if (testWord.StartsWith("#") || testWord.StartsWith("@"))
{
ResetCurrentState();
}
break;
}
}
if (found) { break; }
}
}
if (!CurrentState.IsInComment && !CurrentState.IsInCommentBlock)
{
#region SWITCH
if (words[i].EndsWith(";"))
{
retVal = SetStateReturnValue(retVal);
ResetCurrentState();
return retVal;
}
if ((CurrentState.IsInCreate || CurrentState.IsInDrop && (words[i].ToLower() == "procedure" || words[i].ToLower() == "proc")) && (lineNo > _tripLine ? 1000 : _currentWord - _tripWord) < _offsetTollerance)
ResetCurrentState();
switch (words[i].ToLower())
{
case "insert":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInInsert = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "update":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInUpdate = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "delete":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInDelete = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "into":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
//retVal = SetStateReturnValue(retVal, lineNo);
//if (retVal)
// return retVal;
CurrentState.IsInInto = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "create":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInCreate = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "drop":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInDrop = true;
_tripLine = lineNo;
continue;
case "alter":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInAlter = true;
_tripLine = lineNo;
_tripWord = _currentWord;
continue;
case "truncate":
//assume that we have parsed all lines/words and got to next keyword, so return previous state
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
CurrentState.IsInTruncate = true;
_tripLine = lineNo;
_tripWord = _currentWord;
break;
default:
break;
}
#endregion
if (CurrentState.IsInInsert || CurrentState.IsInDelete || CurrentState.IsInUpdate || CurrentState.IsInDrop || CurrentState.IsInAlter || CurrentState.IsInTruncate || CurrentState.IsInInto)
{
if ((words[i].StartsWith("#") || words[i].StartsWith("@") || words[i].StartsWith("dbo.#") || words[i].StartsWith("dbo.@")) && (lineNo > _tripLine ? 1000 : _currentWord - _tripWord) < _offsetTollerance)
{
ResetCurrentState();
continue;
}
}
if ((CurrentState.IsInInsert || CurrentState.IsInInto || CurrentState.IsInUpdate) && (((_currentWord != _tripWord) && (lineNo > _tripLine ? 1000 : _currentWord - _tripWord) < _offsetTollerance) || (lineNo > _tripLine)))
{
retVal = SetStateReturnValue(retVal);
if (retVal)
return retVal;
}
}
}
return retVal;
}
private static bool SetStateReturnValue(bool retVal)
{
if (CurrentState.IsInInsert ||
CurrentState.IsInDelete ||
CurrentState.IsInUpdate ||
CurrentState.IsInDrop ||
CurrentState.IsInAlter ||
CurrentState.IsInTruncate)
{
retVal = (CurrentState.IsInInsert ||
CurrentState.IsInDelete ||
CurrentState.IsInUpdate ||
CurrentState.IsInDrop ||
CurrentState.IsInAlter ||
CurrentState.IsInTruncate);
}
return retVal;
}
}
}
ИСПОЛЬЗОВАНИЕ
var fileResult = new StateEngine().ParseFile(*path and filename*);