Показать сообщение отдельно
Старый 28.04.2008, 13:49   #1  
Logger is offline
Logger
Участник
Лучший по профессии 2015
Лучший по профессии 2014
 
3,875 / 3123 (112) ++++++++++
Регистрация: 12.10.2004
Адрес: Москва
Записей в блоге: 2
Обновление данных при переходе на Ax3 SP5 - ошибка. Караул !
Коллеги, выношу на суд общественности.
Либо я глючу, либо в процедуре обновления базы для СП5 содержится ошибка.

В 5-м сервис-паке поменяли значения енумов - под это дело процедура обновления перебивает значения во всех таблицах.
SQL запрос для обновления генерится тут :

X++:
\Classes\ChangeEnumValueInTable_W\sqlQuery
X++:
protected str sqlQuery(DictTable _dictTable, FieldID  _fieldID, int _oldValue, int _newValue)
{
    str     szSql;
    str     dataAreaId, dataAreaIdName;

    ;

    dataAreaIdName  = _dictTable.fieldName(fieldnum(Common, DataAreaId), DbBackend::SQL);

    szSql           = strFmt("update %1 set %2 = %3 where %2 = %4",
                             _dictTable.name(DbBackend::Sql),
                             _dictTable.fieldname(_fieldID, DbBackend::Sql),
                             _newValue,
                             _oldValue);


    if (_dictTable.dataPrCompany())
    {
        dataAreaId = '\'' + curext() + '\'';

        if (SqlSystem::databaseBackendId() == DatabaseId::Oracle)
        {
            dataAreaId = strFmt(new SqlSystem().monocaseFmt(), dataAreaId);
        }

        szSql += " and " + dataAreaIdName + ' = ' + dataAreaId;
    }

    return szSql;
}
Что напрягает :

1. Строка 19
X++:
dataAreaId = '\'' + curext() + '\'';
всегда подставляется текущая компания что неверно для виртуальных компаний. Т.е. если таблица включена в виртуальную компанию, то данные для неё не будут обновляться.

Для лечения можно попробовать поставить так
X++:
dataAreaId = '\'' + curExt2dataareaid(_dictTable.id()) + '\'';
Но есть это верно только в том случае если значения енумов поменялись таким образом, что повторное применение процедуры обновления их не портит, т.е. код Idempotency - как сказано в документации. Дело в том что указанная процедура запускается в каждой компании, таким образом если у нас несколько компаний, то данная процедура стартует несколько раз - по числу компаний. Для надежности нужно еще обеспечить чтобы процедура стартовала по виртуальной компании только один раз !

2. Формирование фильтра по DataAreaId для оракла
Строка 23
X++:
dataAreaId = strFmt(new SqlSystem().monocaseFmt(), dataAreaId);
Строка 26
X++:
szSql += " and " + dataAreaIdName + ' = ' + dataAreaId;
функции new SqlSystem().monocaseFmt() не передаются параметры, поэтому она не генерит обрамления вида
SUBSTR(NLS_LOWER(...))
Похоже под ораклом переход вообще не тестировался.

Что это влечет :
1. Некоторые записив БД, для которых значение dataAreaId отличается от возвращаемого curExt() (например не тот регистр оказался) - могут остаться необновленными. Похоже на наше счастье значения DataAreaId сгенерированные ядром при создании записей как раз совпадают по регистру с тем что возвращает curExt() так что все нормально и все записи должны попасть. Но гарантий нет.

2. Поскольку индексы в оракле строятся тоже от функций SUBSTR(NLS_LOWER(DATAAREAID)) - то при выполнении указанных запросов любой индекс будет бесполезен - получится неэффективное сканирование таблицы даже для маленькой компании.

В итоге получился вот такой исправленный код

X++:
protected str sqlQuery(DictTable _dictTable, FieldID  _fieldID, int _oldValue, int _newValue)
{
    str     szSql;
    str     dataAreaId, dataAreaIdName;

    ;

    dataAreaIdName  = _dictTable.fieldName(fieldnum(Common, DataAreaId), DbBackend::SQL);

    szSql           = strFmt("update %1 set %2 = %3 where %2 = %4",
                             _dictTable.name(DbBackend::Sql),
                             _dictTable.fieldname(_fieldID, DbBackend::Sql),
                             _newValue,
                             _oldValue);


    if (_dictTable.dataPrCompany())
    {
        // pkoz 28.04.2008 -->
        //dataAreaId = '\'' + curext() + '\'';
        dataAreaId = '\'' + curExt2dataareaid(_dictTable.id()) + '\''; // перепроверить на корректность для повторных запусков !
        // pkoz 28.04.2008 <--

        if (SqlSystem::databaseBackendId() == DatabaseId::Oracle)
        {
          // pkoz 28.04.2008 -->
            //dataAreaId = strFmt(new SqlSystem().monocaseFmt(), dataAreaId);
            dataAreaId = strFmt(new SqlSystem().monocaseFmt(_dictTable.id(), _fieldID), dataAreaId);
          // pkoz 28.04.2008 <--
        }

        // pkoz 28.04.2008 -->
        if (SqlSystem::databaseBackendId() == DatabaseId::Oracle)
        {
            szSql += " and " + 
                strFmt(new SqlSystem().monocaseFmt(_dictTable.id(), FieldNum(common, DataAreaID)), dataAreaIdName)
                + ' = ' + dataAreaId;
        }
        else
        // pkoz 28.04.2008 <--
        szSql += " and " + dataAreaIdName + ' = ' + dataAreaId;
    }

    return szSql;
}
P.S.
Ax3.0
SP5

В качестве примера для табилцы InventTrans стандартный код сгенерил вот такой запрос
X++:
update INVENTTRANS set TRANSTYPE = 100 where TRANSTYPE = 21 and DATAAREAID = 'dat'
Исправленный код :
X++:
update INVENTTRANS set TRANSTYPE = 100 where TRANSTYPE = 21 and SUBSTR(NLS_LOWER(DATAAREAID),1,3) = SUBSTR(NLS_LOWER('dat'),1,3)

Последний раз редактировалось Logger; 28.04.2008 в 14:00.