CustomSQLColumn: автоматическое предотвращение ошибок, связанных с использованием администрируемых таблиц
Порой возникает необходимость в использовании CustomSQLColumn: не всякий запрос можно построить с помощью обычных сервисов. Ввести небольшое условие, или какой-нибудь расчет, для которого писать «хранимку» нецелесообразно, а вписать его в CustomSQLColumn удобно. Скажем, выводить крестик, если два других поля равны, а если нет – выводить пробел. Особенно в отчётах часто возникает необходимость в поле этого типа. И вот, отчёт создан, отлажен, и вдруг, тестируя уже под пользователем, выясняется, что администрируемые таблицы ему не видны. Поминаются недобрыми словами склероз и забывчивость, когда возникают ошибки вроде «Ошибка открытия источника данных "". Оригинальное сообщение об ошибке: ORA-01031: insufficient privileges», или что-то подобное. Вместо администрируемых таблиц необходимо ставить представления, т.е. view.
Помня об этой особенности, обычно используют в тексте CustomSQLColumn указание на представление, а не на таблицу. Тогда для пользователей-администраторов следует делать подмену view на table. По методу, предложенному Андреем Громовым, на событие BeforeDatasetOpen выполняются проверки:
function ds_OfferingAllowRedemptionOnDatasetBeforeOpen(Dataset, DoOpen) {
if (Connector.CurrentUser.IsAdmin) {
var SelectQuery = Dataset.SelectQuery;
var Filters = SelectQuery.Items(0).Filters;
var TablePrefix = 'tbl_';
var Filter;
var Count = Filters.Count;
for(var i = 0; i Count; i++) {
Filter = Filters.Items(i);
if (Filter.FilterType == ftCustomSQL) {
Filter.SQLText = Filter.SQLText.replace(/vw_/g, TablePrefix);
}
}
}
}
function ds_OfferingOnDatasetBeforeOpen(Dataset, DoOpen) {
if (Connector.CurrentUser.IsAdmin) {
var SelectQuery = Dataset.SelectQuery;
var Select = SelectQuery.Items(0);
var TablePrefix = 'tbl_';
var Columns = Select.Columns;
var Column;
var Count = Columns.Count;
for(var i = 0; i Count; i++) {
Column = Columns.Items(i);
if (Column.ColumnType == ctSQLText) {
Column.SQLText = Column.SQLText.replace(/vw_/g, TablePrefix);
}
}
}
}
Метод замечательно работает, когда CustomSQLColumnы содержат указания на таблицы разделов, администрируемость которых всем известна (вроде tbl_Account или tbl_Contact). Но как быть, когда администриуются не только разделы, но и некоторые детали? Да ещё (как это иногда бывает), деталь сделали администрируемой уже после того, как был создан отчёт. Или во время разработки сервиса SelectQuery просто забыли об этой особенности, и при создании CustomSQLColumn-ов использовали имена таблиц, а не представлений. Отлавливать появившиеся ошибки в куче специфических отчётов, SelectQuery которых содержат десятки CustomSQLColumn было бы крайне медленным и неэффективным занятием. К тому же, ручная правка может наплодить новых ошибок, в чём я очень быстро убедился.
Предлагаемые выше методы оказались неприменимы. Пришлось разработать свой. Нужна была автоматическая проверка доступности всех таблиц, используемых в CustomSQLColumn-ах, и их автоматическая замена. Первоначально было необходимо просто выяснить, из-за каких именно CustomSQLColumn и упоминающихся в них таблиц происходит «свал». Зная, какие именно CustomSQLColumn-ы вызывают ошибку, было куда легче её исправить. Но впоследствии задача была усложнена: раз машина знает, из-за каких таблиц и полей SelectQuery не отработает, так пусть сама их и заменит на представления. После остаётся только передать предлагаемой функции SelectQuery перед открытием: все возможные ошибки, связанные с появлением представлений вместо таблиц будут исправлены автоматически. Функция работает на удивление быстро, и благодаря ей можно пользоваться CustomSQLColumn-ами без опасений, что какая-то из таблиц под пользователем превратится в представление.
Способы, которыми осуществляется проверка, возможно, не самые оптимальные, но подход, полагаю, правильный:
//Функция ищет недоступные таблицы и представления в CustomeSQLColumnах
//и в зависимости от аргумента IsNeededReplace выводит их список в лог (false)
//или автоматически производит замену (true)
//В качестве первого аргумента используется Select Query, где требуется провести поиск/замену
var TablesHash = {};//Ключ - имя таблицы. Значение - имя колонки, через запятую.
// Символы ограничителей имени таблицы:
var QuoteLeft = '"';//Для Оракла - ", для MSSQL - [
var QuoteRight = '"';//Для Оракла - ", для MSSQL - ]
// Также их можно определить автоматически:
/*
if (Connector.DBEngine.DBEngineTypeCode == 'Oracle'){
var QuoteLeft = '"';
var QuoteRight = '"';
} else {
var QuoteLeft = '[';
var QuoteRight = ']';
}
*/
var Prefix = Connector.CurrentUser.IsAdmin?('vw_'):('tbl_');
var ReplacePrefix = Connector.CurrentUser.IsAdmin?('tbl_'):('vw_');
var Select = SQ.Items(0);
var Columns = Select.Columns;
var Column;
var Count = Columns.Count;
for(var i = 0; i Count; i++) {
Column = Columns.Items(i);
if (Column.ColumnType == ctSQLText) { // ctSQLText - константа из scr_SysEnums
var TablesDraftSplit = Column.SQLText.split(QuoteLeft + Prefix);
for (var j = 1; j TablesDraftSplit.length; j++){
var TablesSplit = TablesDraftSplit[j].split(QuoteLeft);
if (Select.Joins.ItemsByLeftTableAlias(Prefix + TablesSplit[0]) == null &&
Select.FromTableAlias != Prefix + TablesSplit[0]){
//Если таблица не входит в число алиасов join-oв и основной таблицы
if (!!TablesHash[TablesSplit[0]]){
if (TablesHash[TablesSplit[0]].indexOf(Column.ColumnAlias) 0 ){
//Добавляем название колонки только если такого ещё нет.
TablesHash[TablesSplit[0]] = TablesHash[TablesSplit[0]] + Column.ColumnAlias + ', ';
}
} else {
TablesHash[TablesSplit[0]] = Column.ColumnAlias +', ';
}
}
}
}
}
//TablesHash содержит список всех используемых таблиц.
//Удаляем лишнюю запятую:
for (var key in TablesHash){
TablesHash[key] = TablesHash[key].substr(0, TablesHash[key].length - 2);
}
//Проверяем доступность каждой таблицы:
for (var key in TablesHash){
//здесь используется проверка "в лоб", можно ли читать из этой таблицы.
//Наверняка существует более грамотный вариант.
var SQL = 'select * from ' + QuoteLeft + Prefix + key + QuoteRight;
try {
Connector.DBEngine.ExecuteCustomSQL(SQL, System.EmptyValue);
} catch (e) {
if (IsNeededReplace){
var RegExpString = QuoteLeft + Prefix + key + QuoteRight;
var Reg = new RegExp(RegExpString, "g");
var ColAliasSplit = TablesHash[key].split(', ');
var ColAliasSplitLength = ColAliasSplit.length;
for (var i = 0; i ColAliasSplitLength; i++){
//для каждого перечисленного поля
SQ.Columns.ItemsByAlias(ColAliasSplit[i]).SQLText =
SQ.Columns.ItemsByAlias(ColAliasSplit[i]).SQLText.replace(Reg,
QuoteLeft + ReplacePrefix + key + QuoteRight);
}
} else {
Log.Write(1, 'Недоступна таблица ' + Prefix + key +
' (используется в колонках ' + TablesHash[key] + ')');
}
}
}
}
"Будак Анатолий Васильевич" написал://здесь используется проверка "в лоб", можно ли читать из этой таблицы.
Так можно же проверить возможность чтения из таблицы по-честному. Метод у Connector.CurrentUser если есть TableGroup у таблицы.