diff --git a/.bsl-language-server.json b/.bsl-language-server.json index 09732ad5..87afa8c9 100644 --- a/.bsl-language-server.json +++ b/.bsl-language-server.json @@ -5,7 +5,7 @@ "listOfIncorrectFirstSymbol": ";|,\\s*\\S+" }, "Typo": { - "userWordsToIgnore": "переиспользование,Переиспользование,переиспользования,Переиспользования,переиспользованию,Переиспользованию,переиспользованием,Переиспользованием,переиспользовано,Переиспользовано,переиспользовать,Переиспользовать,переиспользует,Переиспользует,переиспользуется,Переиспользуется,переиспользуются,Переиспользуются" + "userWordsToIgnore": "Фикстуры,фикстуры,Валидатор,валидатор,валидатора,Валидатора,валидатором,Валидатором,переиспользование,Переиспользование,переиспользования,Переиспользования,переиспользованию,Переиспользованию,переиспользованием,Переиспользованием,переиспользовано,Переиспользовано,переиспользовать,Переиспользовать,переиспользует,Переиспользует,переиспользуется,Переиспользуется,переиспользуются,Переиспользуются,Переподписывающийся,переподписывающийся" } } } diff --git a/README.md b/README.md index 4b75acbf..09f7894e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ * Активная запись * Работа через ХранилищеСущностей * Работа с транзакциями + * Наблюдатели источника данных * Система аннотаций для сущностей * Сущность * Идентификатор @@ -394,6 +395,49 @@ МенеджерСущностей.ЗафиксироватьТранзакцию(); ``` + + +## Наблюдатели источника данных + +Источник данных, с которым работает менеджер сущностей, сообщает зарегистрированным наблюдателям +обо всем, что с ним происходит: об операциях (Сохранить, Получить, Удалить и других), запросах к +СУБД, захвате и освобождении соединений и транзакциях. Каждое событие приходит наблюдателю дважды - +до выполнения и после - одним объектом `СобытиеИсточникаДанных`, в котором после выполнения есть +длительность, ошибка и результат. На этом строятся телеметрия, журнал медленных запросов, подсчет +обращений к БД в тестах. + +Наблюдатель объявляет интерфейс `НаблюдательИсточникаДанных` аннотацией на конструкторе и +реализует два метода: + +```bsl +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Если Событие.Вид() = ВидыСобытийИсточникаДанных.Запрос() И Событие.Длительность() > 100 Тогда + Сообщить(СтрШаблон("Медленный запрос к %1: %2 мс", Событие.ИмяТаблицы(), Событие.Длительность())); + КонецЕсли; +КонецПроцедуры +``` + +Регистрируется наблюдатель на источнике данных, а менеджер создается из источника и отдает +наблюдателю все, что с источником происходит: операции самого менеджера, его хранилищ сущностей +и активной записи, запросы, соединения и транзакции. + +```bsl +Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); +Источник.ДобавитьНаблюдателя(Новый МедленныеЗапросы); +МенеджерСущностей = Новый МенеджерСущностей(Источник); +``` + +Виды событий, их поля, поведение при ошибках и в нескольких потоках, а также поддержка +наблюдателей в собственном коннекторе описаны в разделе +[Наблюдатели источника данных](docs/Наблюдатели.md). + ## Система аннотаций для сущностей @@ -528,6 +572,8 @@ Например, `КоннекторJSON` не умеет работать с транзакциями, однако, он имеет соответствующие методы, выводящие диагностические сообщения при их вызове. +Коннектор **может** дополнительно реализовать интерфейс [`НаблюдаемыйКоннектор`](docs/НаблюдаемыйКоннектор.md): тогда наблюдатели источника данных получают события его запросов к СУБД и описание соединения. Встроенные коннекторы реализуют оба интерфейса. Коннектор без второго интерфейса работает как раньше. + > Важно! Менеджеры и хранилища сущностей одного источника данных собственных коннекторов не держат: они берут соединения из общего пула этого источника. Тип, строка соединения и параметры коннектора задаются при создании ИсточникаДанных. @@ -646,14 +692,18 @@ * [`ОбъектМодели`](src/Классы/ОбъектМодели.os), * [`ЭлементОтбора`](src/Классы/ЭлементОтбора.os); * [`ЭлементПорядка`](src/Классы/ЭлементПорядка.os); + * [`НаблюдательИсточникаДанных`](src/Классы/НаблюдательИсточникаДанных.os); + * [`СобытиеИсточникаДанных`](src/Классы/СобытиеИсточникаДанных.os); * значения модулей-перечислений: * [`ТипыКолонок`](src/Модули/ТипыКолонок.os), * [`ТипыПодчиненныхТаблиц`](src/Модули/ТипыПодчиненныхТаблиц.os), - * [`ВидСравнения`](src/Модули/ВидСравнения.os); + * [`ВидСравнения`](src/Модули/ВидСравнения.os), + * [`ВидыСобытийИсточникаДанных`](src/Модули/ВидыСобытийИсточникаДанных.os); * состав и параметры аннотаций сущностей; * методы ["активной записи"](docs/АктивнаяЗапись.md) сущности; * для Major.Connector: * все публичные методы класса [`АбстрактныйКоннектор`](src/Классы/АбстрактныйКоннектор.os) и их сигнатуры; + * все публичные методы необязательного интерфейса [`НаблюдаемыйКоннектор`](src/Классы/НаблюдаемыйКоннектор.os) и их сигнатуры, а также методы `НачатьЗапрос` и `ЗавершитьЗапрос` реестра наблюдателей, который коннектор получает в `УстановитьНаблюдателей`; * все публичные непомеченные как "нестабильные" (`@unstable`) методы классов: * [`МодельДанных`](src/Классы/МодельДанных.os), * [`ОбъектМодели`](src/Классы/ОбъектМодели.os), @@ -663,6 +713,7 @@ * значения модулей-перечислений: * [`ТипыКолонок`](src/Модули/ТипыКолонок.os), * [`ТипыПодчиненныхТаблиц`](src/Модули/ТипыПодчиненныхТаблиц.os), - * [`ВидСравнения`](src/Модули/ВидСравнения.os). + * [`ВидСравнения`](src/Модули/ВидСравнения.os), + * [`ВидыСобытийИсточникаДанных`](src/Модули/ВидыСобытийИсточникаДанных.os). > To be continued... diff --git a/docs/README.md b/docs/README.md index a6488ac3..3291825f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,13 +13,18 @@ * [ОбъектМодели](ОбъектМодели.md) * [ЭлементОтбора](ЭлементОтбора.md) * [ЭлементПорядка](ЭлементПорядка.md) +* [НаблюдательИсточникаДанных](НаблюдательИсточникаДанных.md) +* [НаблюдаемыйКоннектор](НаблюдаемыйКоннектор.md) +* [СобытиеИсточникаДанных](СобытиеИсточникаДанных.md) ### Перечисления * [ВидСравнения](ВидСравнения.md) +* [ВидыСобытийИсточникаДанных](ВидыСобытийИсточникаДанных.md) * [ТипыКолонок](ТипыКолонок.md) * [ТипыПодчиненныхТаблиц](ТипыПодчиненныхТаблиц.md) ### Прочее * [Потокобезопасная работа с БД](ПотокобезопаснаяРаботаСБД.md) +* [Наблюдатели источника данных](Наблюдатели.md) diff --git "a/docs/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" new file mode 100644 index 00000000..23f21169 --- /dev/null +++ "b/docs/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -0,0 +1,65 @@ +## ВидыСобытийИсточникаДанных + +Виды событий, которые источник данных отдает наблюдателям, см. `Вид()` у +[СобытиеИсточникаДанных](СобытиеИсточникаДанных.md). Значения отдаются функциями, а не экспортными +переменными, чтобы их нельзя было изменить извне. Наблюдатель сравнивает вид события с ними, +а не со строковыми литералами: + +```bsl +Если Событие.Вид() = ВидыСобытийИсточникаДанных.Запрос() Тогда +``` + +### Операция + +```bsl +// Операция менеджера или хранилища: Сохранить, Получить, Удалить и другие. +// +// Возвращаемое значение: +// Строка - Вид события операции. +// +Функция Операция() Экспорт +``` + +### Запрос + +```bsl +// Запрос коннектора к СУБД: SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, ROLLBACK. +// +// Возвращаемое значение: +// Строка - Вид события запроса. +// +Функция Запрос() Экспорт +``` + +### Соединение + +```bsl +// Захват и освобождение соединения пула: Занять, Освободить. +// +// Возвращаемое значение: +// Строка - Вид события соединения. +// +Функция Соединение() Экспорт +``` + +### Транзакция + +```bsl +// Транзакция потока исполнения от начала до исхода. +// +// Возвращаемое значение: +// Строка - Вид события транзакции. +// +Функция Транзакция() Экспорт +``` + +### Виды + +```bsl +// Все виды событий в порядке объявления. +// +// Возвращаемое значение: +// ФиксированныйМассив из Строка - Операция, Запрос, Соединение, Транзакция. +// +Функция Виды() Экспорт +``` diff --git "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" index f6c25745..e0cf8174 100644 --- "a/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -13,8 +13,14 @@ ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); +Источник.ДобавитьНаблюдателя(Новый МойНаблюдатель); + +МенеджерСущностей = Новый МенеджерСущностей(Источник); ``` +Реестр наблюдателей общий у источника, его пула и всех менеджеров, созданных из него, см. +[Наблюдатели источника данных](Наблюдатели.md). + ### ПриСозданииОбъекта ```bsl @@ -150,3 +156,47 @@ // Функция ПулСоединений() Экспорт ``` + +### ДобавитьНаблюдателя + +```bsl +// Регистрирует наблюдателя источника данных. +// +// Наблюдатель получает события всех операций, запросов, соединений и транзакций каждого +// менеджера, созданного из этого источника: самого менеджера, его хранилищ сущностей и +// Active Record. Реестр общий у источника и его менеджеров, поэтому регистрировать можно +// и после создания менеджера. См. интерфейс НаблюдательИсточникаДанных и класс +// СобытиеИсточникаДанных. +// +// Параметры: +// Наблюдатель - Произвольный - Объект с аннотацией &Реализует("НаблюдательИсточникаДанных") на конструкторе, +// реализующий этот интерфейс. Неполная реализация отвергается при регистрации. +// +// Пример: +// Источник.ДобавитьНаблюдателя(Новый МойНаблюдатель); +// +Процедура ДобавитьНаблюдателя(Наблюдатель) Экспорт +``` + +### УдалитьНаблюдателя + +```bsl +// Снимает наблюдателя источника данных с регистрации. +// +// Параметры: +// Наблюдатель - Произвольный - Ранее зарегистрированный наблюдатель. Незарегистрированный пропускается. +// +Процедура УдалитьНаблюдателя(Наблюдатель) Экспорт +``` + +### Наблюдатели + +```bsl +// @internal +// Реестр наблюдателей источника: источник передает его своему пулу соединений. +// +// Возвращаемое значение: +// НаблюдателиИсточникаДанных - Реестр наблюдателей. +// +Функция Наблюдатели() Экспорт +``` diff --git "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" index 2e373405..dd16e32b 100644 --- "a/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" +++ "b/docs/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.md" @@ -6,14 +6,15 @@ // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного -// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его -// временем жизни управляет тот, кто источник создал. +// источника делят соединения, транзакции потоков и наблюдателей. Параметры соединения задает +// источник, а его временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); +// Источник.ДобавитьНаблюдателя(Новый МойНаблюдатель); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // Процедура ПриСозданииОбъекта(Знач ИсточникДанных) diff --git "a/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.md" "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.md" new file mode 100644 index 00000000..7a8e2c4f --- /dev/null +++ "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.md" @@ -0,0 +1,54 @@ +## НаблюдаемыйКоннектор + +Интерфейс коннектора, умеющего сообщать наблюдателям источника данных о запросах к СУБД. + +Дополняет [АбстрактныйКоннектор](АбстрактныйКоннектор.md), а не заменяет его: коннектор без +этого интерфейса работает как раньше, только наблюдатели не получают событий уровня запроса и +описания соединения. Встроенные коннекторы объявляют оба интерфейса аннотацией `&Реализует` +на конструкторе. + +Пул соединений после открытия коннектора передает ему реестр наблюдателей и запрашивает +описание соединения. Описание никогда не содержит секретов: пароль из строки соединения +в него не попадает. + +Подробнее - в разделе [Наблюдатели источника данных](Наблюдатели.md). + +### ПриСозданииОбъекта + +```bsl +// Пример: +// &Реализует("АбстрактныйКоннектор") +// &Реализует("НаблюдаемыйКоннектор") +// Процедура ПриСозданииОбъекта() +// КонецПроцедуры +// +&Интерфейс +Процедура ПриСозданииОбъекта() +``` + +### УстановитьНаблюдателей + +```bsl +// Передает коннектору реестр наблюдателей, которым он сообщает о запросах. +// +// Параметры: +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. Коннектор создает события через +// НачатьЗапрос и завершает через ЗавершитьЗапрос; при пустом реестре события не создаются. +// +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт +``` + +### ОписаниеСоединения + +```bsl +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Описание соединения без секретов: +// * Система - Строка - СУБД: postgresql, sqlite, inmemory, json. +// * Адрес - Строка - Адрес сервера; пустая строка, если сервера нет. +// * Порт - Число - Порт сервера; 0, если сервера нет. +// * БазаДанных - Строка - Имя базы, путь к файлу или каталогу. +// +Функция ОписаниеСоединения() Экспорт +``` diff --git "a/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270.md" "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270.md" new file mode 100644 index 00000000..23ba8780 --- /dev/null +++ "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270.md" @@ -0,0 +1,208 @@ +## Наблюдатели источника данных + +### Зачем + +Наблюдатель - способ узнать, что происходит с источником данных, не меняя ни менеджер сущностей, +ни коннектор: сколько длится операция, какой запрос ушел в СУБД и сколько строк вернул, сколько +поток ждал соединение, чем закончилась транзакция. На этом строятся телеметрия (трейсы и метрики), +журнал медленных запросов, подсчет обращений к БД в тестах. + +Наблюдатели регистрируются на [источнике данных](ИсточникДанных.md), а менеджер создается из +источника. Наблюдатель видит все, что происходит с источником: операции менеджера, его хранилищ +сущностей и активной записи, запросы, соединения и транзакции. + +### Наблюдатель + +Наблюдатель - объект с аннотацией `&Реализует("НаблюдательИсточникаДанных")` на конструкторе и двумя +методами: `ПередСобытием` и `ПослеСобытия`. Оба получают один и тот же объект +[СобытиеИсточникаДанных](СобытиеИсточникаДанных.md): до выполнения в нем заполнены описательные поля, после - +длительность, ошибка и результат. + +```bsl +// Класс МедленныеЗапросы + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Если Событие.Вид() = ВидыСобытийИсточникаДанных.Запрос() И Событие.Длительность() > 100 Тогда + Сообщить(СтрШаблон("Медленный %1 к %2: %3 мс", Событие.Операция(), Событие.ИмяТаблицы(), Событие.Длительность())); + КонецЕсли; +КонецПроцедуры +``` + +Регистрация - методом `ДобавитьНаблюдателя` источника данных, снятие - `УдалитьНаблюдателя`. +Реестр у источника и созданных из него менеджеров общий, поэтому регистрировать можно в любой +момент, но наблюдатель, добавленный до `Инициализировать`, увидит и саму инициализацию. +Регистрация проверяет реализацию через библиотеку extends: объект без аннотации отвергается с +сообщением, какой аннотации не хватает, а объявивший интерфейс, но не реализовавший его целиком - +сообщением валидатора о нереализованном интерфейсе. + +```bsl +Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); +Источник.ДобавитьНаблюдателя(Новый МедленныеЗапросы); + +МенеджерСущностей = Новый МенеджерСущностей(Источник); +МенеджерСущностей.ДобавитьКлассВМодель(Тип("Автор")); +МенеджерСущностей.Инициализировать(); +``` + +Наблюдателей может быть несколько, они вызываются в порядке регистрации. Реестр общий на +источник данных: у менеджеров и хранилищ сущностей своего нет. Состав получателей фиксируется при начале +события: наблюдатель, снятый между фазами, все равно получает `ПослеСобытия` начатого при нем +события, а добавленный между фазами видит только события, начатые после регистрации. + +### События + +У события есть вид, операция и две фазы. Виды - значения перечисления +[ВидыСобытийИсточникаДанных](ВидыСобытийИсточникаДанных.md), с ними наблюдатель и сравнивает `Вид()`. +Что заполнено в какой фазе: + +| Вид | Операция | До выполнения | После выполнения | +| --- | --- | --- | --- | +| Операция | Сохранить, Получить, ПолучитьОдно, Удалить, Инициализировать, ВыполнитьСКоннектором, ВычислитьСКоннектором | ТипСущности, ИмяТаблицы, Глубина | КоличествоСтрок у чтения | +| Запрос | SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, ROLLBACK | ИмяТаблицы, ТекстЗапроса | КоличествоСтрок у SELECT | +| Соединение | Занять | - | Ожидание, Открыто, ВремяОткрытия; снимок пула: Занято, Свободно, Ожидает, Предел | +| Соединение | Освободить | - | Снимок пула: Занято, Свободно, Ожидает, Предел | +| Транзакция | Транзакция | - | Исход: commit, rollback, failed, abandoned | + +В любом событии заполнены `ТипКоннектора`, `Система`, `Адрес`, `Порт`, `БазаДанных` и `Начало`, +а после выполнения - `Длительность` и `Ошибка`. Описание соединения никогда не содержит пароля: +коннектор отдает его отдельной структурой, а не строкой соединения. Коннектор отдает описание +после открытия, поэтому у событий, начатых до открытия первого соединения (инициализация и ее +захват соединения), `Адрес`, `Порт` и `БазаДанных` в фазе `ПередСобытием` еще пусты и заполняются +к фазе `ПослеСобытия`. + +**Операция** - вызов менеджера или хранилища из прикладного кода. Операция, порожденная другой +операцией (разыменование ссылки на сущность при чтении, чтение подчиненных таблиц), приходит с +`Глубина` на единицу больше. Наблюдатель, которому нужны только прикладные вызовы, отбирает +события с нулевой глубиной; наблюдатель, строящий дерево спанов, вкладывает их по глубине. + +**Запрос** - обращение коннектора к СУБД. Его отдают коннекторы, реализующие +[НаблюдаемыйКоннектор](НаблюдаемыйКоннектор.md); встроенные коннекторы реализуют его все. +`ТекстЗапроса` содержит SQL с плейсхолдерами, а не значениями параметров; у коннекторов без SQL +(InMemory, JSON) он `Неопределено`. + +**Соединение** - работа пула. `Занять` сообщает, досталось ли потоку свободное соединение или +пришлось открыть новое (`Открыто`, `ВремяОткрытия`) и сколько поток простоял в очереди +(`Ожидание`). Началом обоих событий отмечен вход в пул, поэтому их `Длительность` - полное время +работы с пулом, включая ожидание его внутренней блокировки: у `Занять` - до выдачи соединения +потоку, у `Освободить` - до возврата соединения, вместе с закрытием непригодного. Сама рассылка +идет позже начала: пока пул не отработал, неизвестно, состоялся ли захват или возврат, а звать +наблюдателя из-под блокировки пула нельзя. Оба события несут снимок пула после себя - по нему +считаются метрики занятости. +Повторный захват тем же потоком - операция внутри транзакции или вложенная операция - событий не +порождает: соединение у потока уже есть. А вот соединение, которое поток не освободил сам, дает +`Освободить` тогда, когда его возвращает в пул завершение этого потока. Неудачный возврат события +не отменяет: соединение к тому моменту уже отобрано у потока, поэтому `Освободить` приходит с +ошибкой в `Ошибка` - обычно это ошибка закрытия непригодного соединения. + +**Транзакция** - от `НачатьТранзакцию` до исхода; событие начинается до `BEGIN`, поэтому запрос +`BEGIN` вложен в него. `commit` и `rollback` - штатное завершение; `failed` - начало, фиксация или +отмена завершились ошибкой, она в `Ошибка`; `abandoned` - поток +исполнения завершился, не закрыв транзакцию, и пул откатил ее при возврате соединения. + +### Ошибки + +Ошибка операции или запроса попадает в `Ошибка` события, а затем уходит вызывающему как обычно: +наблюдатель ее видит, но не перехватывает. + +Ошибка самого наблюдателя операцию не прерывает: она пишется предупреждением в лог +`oscript.lib.entity.observers`, и работа продолжается. Наблюдение - вспомогательная функция, и падать из-за +телеметрии прикладной код не должен. + +### Потоки исполнения + +Наблюдателя никогда не зовут, пока пул держит свою внутреннюю блокировку: события, случившиеся +под ней - брошенная транзакция при возврате соединения и ее откат, - рассылаются сразу после +того, как блокировка снята. Поэтому медленный наблюдатель задерживает только свой поток, а не +работу пула у всех остальных. + +У такого события обе фазы приходят задним числом, одна за другой: событие к моменту рассылки уже +завершено, поэтому в `ПередСобытием` у него заполнены и `Длительность`, и `Ошибка`. Настенные часы +наблюдателя тут ничего не измерят - спан длиной в ноль вместо настоящего отката. Границы такого +спана строятся по самому событию, из `Начало()` и `Длительность()`; OpenTelemetry выставить их явно +позволяет. Так приходит откат брошенной транзакции - единственный запрос, который пул делает под +своей блокировкой. + +Пулом наблюдатель не управляет: события его не отдают, и звать `Занять` или `Освободить` у пула +источника обработчику нельзя. Захват считается по парам, и лишнее освобождение вернет в пул +соединение, которым занятая им операция еще пользуется, - его тут же получит другой поток. Своя +работа с БД из обработчика этому не мешает, пока идет через менеджер: вложенный захват +реентерабельный и глубину до нуля не опускает. + +Наблюдатель вызывается из всех потоков исполнения, работающих с менеджером, одновременно. Общее +состояние наблюдателя должно быть синхронизировано, а то, что нужно передать от `ПередСобытием` +к `ПослеСобытия` - открытый спан, момент начала, - кладется в само событие: + +```bsl +Процедура ПередСобытием(Событие) Экспорт + Событие.УстановитьСостояние(Трассировщик.НачатьСпан(Событие.Операция())); +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Спан = Событие.Состояние(); + Если Событие.Ошибка() <> Неопределено Тогда + Спан.ЗаписатьИсключение(Событие.Ошибка()); + КонецЕсли; + Спан.Завершить(); +КонецПроцедуры +``` + +Событие принадлежит одному потоку и между потоками не передается, поэтому состояние в нем +синхронизации не требует. Состояние у каждого наблюдателя свое: событие знает, кому оно +рассылается сейчас, и `Состояние()` возвращает то, что положил именно этот наблюдатель. +Несколько наблюдателей на одном менеджере друг другу не мешают. + +### Свой коннектор + +Коннектор, написанный вне библиотеки, наблюдателей не замечает и работает как раньше: события +операций, соединений и транзакций приходят и с ним, только без запросов, а описание соединения +сводится к имени типа коннектора в нижнем регистре в поле `Система`. + +Чтобы отдавать и запросы, коннектор объявляет второй интерфейс и реализует два метода: + +```bsl +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт + РеестрНаблюдателей = Наблюдатели; +КонецПроцедуры + +Функция ОписаниеСоединения() Экспорт + Возврат Новый Структура("Система, Адрес, Порт, БазаДанных", "mydb", "db.local", 1234, "test"); +КонецФункции +``` + +Реестр коннектор получает сразу после открытия. Событие запроса коннектор начинает и +завершает сам, обернув обращение к СУБД: + +```bsl +Событие = РеестрНаблюдателей.НачатьЗапрос("SELECT", ИмяТаблицы, ТекстЗапроса); +Попытка + Строки = ВыполнитьВСУБД(ТекстЗапроса); +Исключение + РеестрНаблюдателей.ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); + ВызватьИсключение; +КонецПопытки; +РеестрНаблюдателей.ЗавершитьЗапрос(Событие, , Строки.Количество()); +``` + +Без зарегистрированных наблюдателей `НачатьЗапрос` возвращает `Неопределено`, а `ЗавершитьЗапрос` +с ним ничего не делает: коннектору не нужно проверять, есть ли кому слушать. Описание соединения +не должно содержать пароля и других секретов. + +### Накладные расходы + +Без наблюдателей события не создаются: реестр только проверяет, что он пуст. С наблюдателями на +каждую операцию приходится один объект события и два вызова каждого наблюдателя, плюс то же на +каждый запрос, захват соединения и транзакцию. Стоимость наблюдения определяется самим +наблюдателем; тяжелую работу - отправку телеметрии, запись в файл - ему следует выполнять +асинхронно. diff --git "a/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" new file mode 100644 index 00000000..61645be4 --- /dev/null +++ "b/docs/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -0,0 +1,65 @@ +## НаблюдательИсточникаДанных + +Интерфейс наблюдателя источника данных. + +Наблюдатель получает события источника данных, с которым работает менеджер сущностей: +операции (Сохранить, Получить, ПолучитьОдно, Удалить, Инициализировать, ВыполнитьСКоннектором, +ВычислитьСКоннектором), запросы к СУБД, захват и освобождение соединений, транзакции. +Каждое событие приходит дважды: до выполнения и после, одним и тем же объектом +[СобытиеИсточникаДанных](СобытиеИсточникаДанных.md). + +Реализация объявляет интерфейс аннотацией `&Реализует("НаблюдательИсточникаДанных")` на конструкторе +и регистрируется на источнике данных методом `ДобавитьНаблюдателя`; менеджер, созданный из +источника, отдает ей события. Регистрация проверяет реализацию через `ВалидаторРеализации` +библиотеки extends, поэтому неполная реализация отвергается сразу. + +Наблюдатель вызывается из разных потоков исполнения одновременно и не должен держать +несинхронизированного общего состояния: все, что нужно между фазами, кладется в событие +методом `УстановитьСостояние`; у каждого наблюдателя это состояние свое. Исключение наблюдателя +пишется в лог и операцию не прерывает. + +Подробнее - в разделе [Наблюдатели источника данных](Наблюдатели.md). + +### ПриСозданииОбъекта + +```bsl +// Пример: +// &Реализует("НаблюдательИсточникаДанных") +// Процедура ПриСозданииОбъекта() +// КонецПроцедуры +// +// Процедура ПередСобытием(Событие) Экспорт +// Событие.УстановитьСостояние(ТекущаяУниверсальнаяДатаВМиллисекундах()); +// КонецПроцедуры +// +// Процедура ПослеСобытия(Событие) Экспорт +// Сообщить(СтрШаблон("%1 %2: %3 мс", Событие.Вид(), Событие.Операция(), Событие.Длительность())); +// КонецПроцедуры +// +&Интерфейс +Процедура ПриСозданииОбъекта() +``` + +### ПередСобытием + +```bsl +// Вызывается перед выполнением события. +// +// Параметры: +// Событие - СобытиеИсточникаДанных - Начатое событие. Поля результата обычно еще не заполнены; +// у события, рассылка которого отложена, заполнены и они: см. Наблюдатели.md, раздел про +// потоки исполнения. +// +Процедура ПередСобытием(Событие) Экспорт +``` + +### ПослеСобытия + +```bsl +// Вызывается после выполнения события, в том числе завершившегося ошибкой. +// +// Параметры: +// Событие - СобытиеИсточникаДанных - Завершенное событие: заполнены Длительность, Ошибка и поля результата. +// +Процедура ПослеСобытия(Событие) Экспорт +``` diff --git "a/docs/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" new file mode 100644 index 00000000..8acadf4e --- /dev/null +++ "b/docs/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -0,0 +1,478 @@ +## СобытиеИсточникаДанных + +Событие источника данных, передаваемое наблюдателям. + +Один и тот же объект приходит наблюдателю дважды: в `ПередСобытием`, когда заполнены только +описательные поля, и в `ПослеСобытия`, когда заполнены длительность, ошибка и поля результата. +Поля читаются функциями; менять их наблюдателю не следует. Для своих данных между фазами +у наблюдателя есть `Состояние` - у каждого наблюдателя свое, чужого он не видит. + +Виды событий - значения перечисления [ВидыСобытийИсточникаДанных](ВидыСобытийИсточникаДанных.md). Заполняемые поля: + +* Операция - операция менеджера или хранилища: Операция, ТипСущности, ИмяТаблицы, Глубина; + после - КоличествоСтрок для чтения. +* Запрос - запрос к СУБД: Операция (SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, + ROLLBACK), ИмяТаблицы, ТекстЗапроса (Неопределено у коннекторов без SQL); после - КоличествоСтрок. +* Соединение - захват и освобождение соединения пула: Операция (Занять, Освободить); после - + снимок пула: Занято, Свободно, Ожидает, Предел. Ожидание, Открыто и ВремяОткрытия описывают + захват, поэтому заполнены только у Занять. +* Транзакция - транзакция потока исполнения: после - Исход (commit, rollback, failed, abandoned). + +Методы с пометкой `@internal` заполняют событие изнутри библиотеки. + +Подробнее - в разделе [Наблюдатели источника данных](Наблюдатели.md). + +### ПриСозданииОбъекта + +```bsl +// Создает начатое событие. +// +// Создается библиотекой, напрямую конструировать не следует. +// +// Параметры: +// ПВид - Строка - Вид события, значение перечисления ВидыСобытийИсточникаДанных. +// ПОперация - Строка - Операция события. +// ПГлубина - Число - Вложенность операции. +// +Процедура ПриСозданииОбъекта(Знач ПВид, Знач ПОперация, Знач ПГлубина = 0) +``` + +### Вид + +```bsl +// Вид события. +// +// Возвращаемое значение: +// Строка - Значение перечисления ВидыСобытийИсточникаДанных: Операция, Запрос, Соединение или Транзакция. +// +Функция Вид() Экспорт +``` + +### Операция + +```bsl +// Операция события. +// +// Возвращаемое значение: +// Строка - Имя операции, см. описание класса. +// +Функция Операция() Экспорт +``` + +### Глубина + +```bsl +// Вложенность операции. +// +// Возвращаемое значение: +// Число - 0 у операции из прикладного кода, 1 у порожденной ею разыменованием ссылки и т.д. +// +Функция Глубина() Экспорт +``` + +### ТипСущности + +```bsl +// Тип сущности операции. +// +// Возвращаемое значение: +// Тип - Тип сущности. +// Неопределено - Операция не привязана к сущности. +// +Функция ТипСущности() Экспорт +``` + +### ИмяТаблицы + +```bsl +// Имя таблицы сущности или запроса. +// +// Возвращаемое значение: +// Строка - Имя таблицы; пустая строка, если таблицы нет. +// +Функция ИмяТаблицы() Экспорт +``` + +### ТекстЗапроса + +```bsl +// Текст запроса к СУБД. +// +// Возвращаемое значение: +// Строка - Текст запроса с плейсхолдерами параметров. +// Неопределено - Коннектор не формирует текст запроса. +// +Функция ТекстЗапроса() Экспорт +``` + +### КоличествоСтрок + +```bsl +// Число строк результата чтения. +// +// Возвращаемое значение: +// Число - Число строк. +// Неопределено - Событие не читает строки или еще не завершено. +// +Функция КоличествоСтрок() Экспорт +``` + +### ТипКоннектора + +```bsl +// Тип коннектора пула. +// +// Возвращаемое значение: +// Тип - Тип класса коннектора. +// +Функция ТипКоннектора() Экспорт +``` + +### Система + +```bsl +// Система управления базой данных. +// +// Возвращаемое значение: +// Строка - postgresql, sqlite, inmemory, json или имя типа коннектора в нижнем регистре. +// +Функция Система() Экспорт +``` + +### Адрес + +```bsl +// Адрес сервера СУБД. +// +// Возвращаемое значение: +// Строка - Адрес; пустая строка, если у СУБД нет сервера. +// +Функция Адрес() Экспорт +``` + +### Порт + +```bsl +// Порт сервера СУБД. +// +// Возвращаемое значение: +// Число - Порт; 0, если у СУБД нет сервера. +// +Функция Порт() Экспорт +``` + +### БазаДанных + +```bsl +// Имя базы данных. +// +// Возвращаемое значение: +// Строка - Имя базы, путь к файлу или строка подключения без секретов. +// +Функция БазаДанных() Экспорт +``` + +### Начало + +```bsl +// Момент начала события. +// +// Может быть раньше рассылки ПередСобытием: событие захвата соединения начинается со входа +// в пул, а разослано будет, когда станет известно, что событие вообще нужно. +// +// Возвращаемое значение: +// Число - Универсальная дата в миллисекундах. +// +Функция Начало() Экспорт +``` + +### Длительность + +```bsl +// Длительность события. +// +// У события, рассылка которого отложена, заполнена уже в ПередСобытием: такое событие приходит +// наблюдателю задним числом, обеими фазами подряд. См. Наблюдатели.md, "Потоки исполнения". +// +// Возвращаемое значение: +// Число - Миллисекунды от начала до завершения. +// Неопределено - Событие еще не завершено. +// +Функция Длительность() Экспорт +``` + +### Ошибка + +```bsl +// Ошибка, которой завершилось событие. +// +// Возвращаемое значение: +// ИнформацияОбОшибке - Ошибка. +// Неопределено - Событие завершилось успешно или еще не завершено. +// +Функция Ошибка() Экспорт +``` + +### Состояние + +```bsl +// Данные наблюдателя, сохраненные между фазами. +// +// У каждого наблюдателя свое состояние: возвращается то, что в ПередСобытием положил +// наблюдатель, которому событие рассылается сейчас. +// +// Возвращаемое значение: +// Произвольный - Что положил наблюдатель; Неопределено, если ничего. +// +Функция Состояние() Экспорт +``` + +### УстановитьСостояние + +```bsl +// Сохраняет данные наблюдателя до фазы ПослеСобытия. +// +// Состояние принадлежит наблюдателю, который его положил: другие наблюдатели того же +// события его не видят и не затирают. +// +// Параметры: +// Значение - Произвольный - Данные наблюдателя, например открытый спан. +// +Процедура УстановитьСостояние(Значение) Экспорт +``` + +### УстановитьТекущегоНаблюдателя + +```bsl +// @internal +// Отмечает наблюдателя, которому событие рассылается сейчас: Состояние читается и пишется для него. +// +// Параметры: +// Наблюдатель - НаблюдательИсточникаДанных, Неопределено - Наблюдатель; Неопределено - рассылка окончена. +// +Процедура УстановитьТекущегоНаблюдателя(Знач Наблюдатель) Экспорт +``` + +### Получатели + +```bsl +// @internal +// Получатели события: наблюдатели, зарегистрированные на момент его создания. +// +// Возвращаемое значение: +// ФиксированныйМассив из НаблюдательИсточникаДанных - Кому событие рассылается в обеих фазах. +// +Функция Получатели() Экспорт +``` + +### УстановитьПолучателей + +```bsl +// @internal +// Фиксирует получателей события: обе фазы уходят одному и тому же снимку реестра. +// +// Параметры: +// ПНаблюдатели - ФиксированныйМассив из НаблюдательИсточникаДанных - Снимок реестра. +// +Процедура УстановитьПолучателей(Знач ПНаблюдатели) Экспорт +``` + +### Исход + +```bsl +// Исход транзакции. +// +// Возвращаемое значение: +// Строка - commit, rollback, failed или abandoned. +// Неопределено - Событие не о транзакции или еще не завершено. +// +Функция Исход() Экспорт +``` + +### Ожидание + +```bsl +// Время ожидания соединения в очереди пула. +// +// Возвращаемое значение: +// Число - Миллисекунды; 0, если соединение досталось сразу. +// Неопределено - Событие не о захвате соединения. +// +Функция Ожидание() Экспорт +``` + +### Открыто + +```bsl +// Признак открытия нового соединения при захвате. +// +// Возвращаемое значение: +// Булево - Истина, если пул открыл новое соединение, а не выдал свободное. +// Неопределено - Событие не о захвате соединения. +// +Функция Открыто() Экспорт +``` + +### ВремяОткрытия + +```bsl +// Время открытия нового соединения. +// +// Возвращаемое значение: +// Число - Миллисекунды; 0, если соединение не открывалось. +// Неопределено - Событие не о захвате соединения. +// +Функция ВремяОткрытия() Экспорт +``` + +### Занято + +```bsl +// Соединений, занятых потоками исполнения, после события. +// +// Возвращаемое значение: +// Число - Занятые соединения. +// Неопределено - Событие не о соединении. +// +Функция Занято() Экспорт +``` + +### Свободно + +```bsl +// Свободных соединений в пуле после события. +// +// Возвращаемое значение: +// Число - Свободные соединения. +// Неопределено - Событие не о соединении. +// +Функция Свободно() Экспорт +``` + +### Ожидает + +```bsl +// Потоков исполнения в очереди за соединением после события. +// +// Возвращаемое значение: +// Число - Ожидающие. +// Неопределено - Событие не о соединении. +// +Функция Ожидает() Экспорт +``` + +### Предел + +```bsl +// Размер пула. +// +// Возвращаемое значение: +// Число - Предел одновременно живущих соединений; 0 - без предела. +// Неопределено - Событие не о соединении. +// +Функция Предел() Экспорт +``` + +### УстановитьСущность + +```bsl +// @internal +// Привязывает событие к сущности. +// +// Параметры: +// ПТипСущности - Тип - Тип сущности. +// ПИмяТаблицы - Строка - Имя таблицы. +// +Процедура УстановитьСущность(Знач ПТипСущности, Знач ПИмяТаблицы) Экспорт +``` + +### УстановитьЗапрос + +```bsl +// @internal +// Заполняет запрос к СУБД. +// +// Параметры: +// ПИмяТаблицы - Строка - Имя таблицы запроса. +// ПТекстЗапроса - Строка, Неопределено - Текст запроса. +// +Процедура УстановитьЗапрос(Знач ПИмяТаблицы, Знач ПТекстЗапроса) Экспорт +``` + +### УстановитьКоннектор + +```bsl +// @internal +// Заполняет сведения о коннекторе и соединении. +// +// Параметры: +// ПТипКоннектора - Тип - Тип коннектора. +// Описание - Структура - Система, Адрес, Порт, БазаДанных. +// +Процедура УстановитьКоннектор(Знач ПТипКоннектора, Знач Описание) Экспорт +``` + +### УстановитьКоличествоСтрок + +```bsl +// @internal +// Заполняет число строк результата. +// +// Параметры: +// ПКоличествоСтрок - Число - Число строк. +// +Процедура УстановитьКоличествоСтрок(Знач ПКоличествоСтрок) Экспорт +``` + +### УстановитьИсход + +```bsl +// @internal +// Заполняет исход транзакции. +// +// Параметры: +// ПИсход - Строка - commit, rollback, failed или abandoned. +// +Процедура УстановитьИсход(Знач ПИсход) Экспорт +``` + +### УстановитьДанныеЗахвата + +```bsl +// @internal +// Заполняет сведения о захвате соединения. +// +// Параметры: +// ПОжидание - Число - Ожидание в очереди, мс. +// ПОткрыто - Булево - Открыто ли новое соединение. +// ПВремяОткрытия - Число - Время открытия, мс. +// +Процедура УстановитьДанныеЗахвата(Знач ПОжидание, Знач ПОткрыто, Знач ПВремяОткрытия) Экспорт +``` + +### УстановитьСнимокПула + +```bsl +// @internal +// Заполняет снимок пула после события. +// +// Параметры: +// ПЗанято - Число - Занятые соединения. +// ПСвободно - Число - Свободные соединения. +// ПОжидает - Число - Ожидающие потоки исполнения. +// ППредел - Число - Размер пула. +// +Процедура УстановитьСнимокПула(Знач ПЗанято, Знач ПСвободно, Знач ПОжидает, Знач ППредел) Экспорт +``` + +### Завершить + +```bsl +// @internal +// Завершает событие: фиксирует длительность и ошибку. +// +// Параметры: +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка, если событие ею завершилось. +// +Процедура Завершить(Знач ИнформацияОбОшибке = Неопределено) Экспорт +``` diff --git a/lib.config b/lib.config index 5972012a..80b339ac 100644 --- a/lib.config +++ b/lib.config @@ -13,6 +13,9 @@ + + + @@ -21,4 +24,5 @@ + \ No newline at end of file diff --git a/packagedef b/packagedef index f59fecee..c2e7c0c3 100644 --- a/packagedef +++ b/packagedef @@ -24,6 +24,7 @@ .ЗависитОт("lambdas", "0.3.4") .ЗависитОт("logos", "1.7.1") .ЗависитОт("decorator", "3.0.0") + .ЗависитОт("extends", "0.2.0") .ЗависитОт("reflector", "0.7.1") .ЗависитОт("sql", "1.3.3") .ЗависитОт("strings", "0.5.0") diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..37dadc34 --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,502 @@ +#Использовать "../concurrent" + +#Использовать annotations +#Использовать reflector +#Использовать extends +#Использовать logos + +// ФиксированныйМассив - Зарегистрированные наблюдатели. Меняется целиком: событие при создании +// берет текущий снимок и рассылается ему в обеих фазах, какие бы регистрации ни случились между ними. +Перем Наблюдатели; +// БлокировкаЧтенияЗаписи - Защищает замену снимка при одновременной регистрации из разных потоков. +Перем БлокировкаРеестра; +// Тип - Тип коннектора пула. +Перем ТипКоннектора; +// Структура - Система, Адрес, Порт, БазаДанных: чем и куда подключается пул. +Перем ОписаниеСоединения; +// Строка - Ключ вложенности операций в данных потока исполнения. +Перем КлючГлубины; +// Строка - Ключ отложенных рассылок в данных потока исполнения. +Перем КлючОтложенных; +// Лог - Журнал наблюдателей. +Перем Лог; +// ВалидаторРеализации - Проверяет реализацию интерфейса при регистрации. +Перем Валидатор; +// ОпределениеАннотации - Определение аннотации &Реализует для чтения имени интерфейса. +Перем ОпределениеАннотацииРеализует; +// Булево - Объявляет ли коннектор пула интерфейс НаблюдаемыйКоннектор. Неопределено - еще не проверялось. +Перем КоннекторНаблюдаем; + +// Реестр наблюдателей источника данных. +// +// Один на источник данных: пул соединений общий у менеджера и его хранилищ, поэтому события +// всех операций, запросов, соединений и транзакций идут через один реестр. При пустом реестре +// события не создаются: НачатьОперацию возвращает Неопределено, а Завершить с ним ничего не +// делает. +// +// Исключение наблюдателя пишется в лог и не прерывает операцию: наблюдение не должно ломать +// то, за чем наблюдает. +// +// Параметры: +// ПТипКоннектора - Тип - Тип коннектора пула. +// +Процедура ПриСозданииОбъекта(Знач ПТипКоннектора) + + Наблюдатели = Новый ФиксированныйМассив(Новый Массив); + БлокировкаРеестра = Новый БлокировкаЧтенияЗаписи; + ТипКоннектора = ПТипКоннектора; + ОписаниеСоединения = СтрокиСоединения.ОписаниеБезСервера(СистемаПоТипу(ПТипКоннектора), ""); + КлючГлубины = "entity.observers." + Строка(Новый УникальныйИдентификатор); + КлючОтложенных = "entity.observers.deferred." + Строка(Новый УникальныйИдентификатор); + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.observers"); + + КонтейнерАннотаций = Новый КонтейнерАннотаций(); + КонтейнерАннотаций.ДобавитьАннотацию(Тип("АннотацияРеализует")); + КонтейнерАннотаций.ДобавитьАннотацию(Тип("АннотацияИнтерфейс")); + КонтейнерАннотаций.ДобавитьАннотацию(Тип("АннотацияРасширяет")); + ОпределениеАннотацииРеализует = КонтейнерАннотаций.ПолучитьОпределениеАннотации("Реализует"); + Валидатор = Новый ВалидаторРеализации(КонтейнерАннотаций); + +КонецПроцедуры + +// Регистрирует наблюдателя. +// +// Наблюдатель должен объявить интерфейс аннотацией &Реализует("НаблюдательИсточникаДанных") и +// реализовать его целиком; иначе регистрация завершается ошибкой. Повторная регистрация того +// же объекта ничего не меняет. +// +// Параметры: +// Наблюдатель - Произвольный - Объект, реализующий НаблюдательИсточникаДанных. +// +Процедура Добавить(Наблюдатель) Экспорт + + ПроверитьОбъявлениеИнтерфейса(Наблюдатель); + Валидатор.ИнтерфейсыРеализованыКорректно(Наблюдатель); + + // Замена снимка - чтение-изменение-запись: без блокировки две одновременные регистрации + // построили бы новый снимок из одного старого, и одна из них потерялась бы. + БлокировкаРеестра.Запись().Заблокировать(); + + Попытка + + Если Наблюдатели.Найти(Наблюдатель) = Неопределено Тогда + Новые = Новый Массив(Наблюдатели); + Новые.Добавить(Наблюдатель); + Наблюдатели = Новый ФиксированныйМассив(Новые); + КонецЕсли; + + Исключение + БлокировкаРеестра.Запись().Разблокировать(); + ВызватьИсключение; + КонецПопытки; + + БлокировкаРеестра.Запись().Разблокировать(); + +КонецПроцедуры + +// Снимает наблюдателя с регистрации. +// +// Параметры: +// Наблюдатель - Произвольный - Ранее зарегистрированный наблюдатель. Незарегистрированный пропускается. +// +Процедура Удалить(Наблюдатель) Экспорт + + БлокировкаРеестра.Запись().Заблокировать(); + + Попытка + + Индекс = Наблюдатели.Найти(Наблюдатель); + Если Индекс <> Неопределено Тогда + Новые = Новый Массив(Наблюдатели); + Новые.Удалить(Индекс); + Наблюдатели = Новый ФиксированныйМассив(Новые); + КонецЕсли; + + Исключение + БлокировкаРеестра.Запись().Разблокировать(); + ВызватьИсключение; + КонецПопытки; + + БлокировкаРеестра.Запись().Разблокировать(); + +КонецПроцедуры + +// Определяет, зарегистрирован ли хоть один наблюдатель. +// +// Возвращаемое значение: +// Булево - Истина, если наблюдателей нет. +// +Функция Пусто() Экспорт + Возврат Наблюдатели.Количество() = 0; +КонецФункции + +// Число зарегистрированных наблюдателей. +// +// Возвращаемое значение: +// Число - Количество. +// +Функция Количество() Экспорт + Возврат Наблюдатели.Количество(); +КонецФункции + +// Описание соединения, которым помечаются события. +// +// Возвращаемое значение: +// Структура - Система, Адрес, Порт, БазаДанных. +// +Функция ОписаниеСоединения() Экспорт + Возврат ОписаниеСоединения; +КонецФункции + +// Запоминает описание соединения, полученное от коннектора. +// +// Параметры: +// Описание - Структура - Система, Адрес, Порт, БазаДанных. +// +Процедура УстановитьОписаниеСоединения(Знач Описание) Экспорт + ОписаниеСоединения = Описание; +КонецПроцедуры + +// Подключает к реестру только что открытый коннектор пула. +// +// Коннектор, объявивший интерфейс НаблюдаемыйКоннектор, получает реестр и сообщает описание +// соединения; остальные работают без событий уровня запроса. Объявление проверяется один раз +// на тип коннектора, реализация - валидатором extends. +// +// Ответ запоминается только после успешной проверки: иначе коннектор, объявивший интерфейс и не +// реализовавший его, прошел бы валидатор единожды, а следующие открытия соединения жаловались бы +// на ненайденный метод вместо интерфейса. +// +// Параметры: +// Коннектор - АбстрактныйКоннектор - Открытый коннектор. +// +Процедура ПодключитьКоннектор(Коннектор) Экспорт + + Если КоннекторНаблюдаем = Неопределено Тогда + Наблюдаем = ОбъявляетИнтерфейс(Коннектор, "НаблюдаемыйКоннектор"); + Если Наблюдаем Тогда + Валидатор.ИнтерфейсыРеализованыКорректно(Коннектор); + КонецЕсли; + КоннекторНаблюдаем = Наблюдаем; + КонецЕсли; + + Если НЕ КоннекторНаблюдаем Тогда + Возврат; + КонецЕсли; + + Коннектор.УстановитьНаблюдателей(ЭтотОбъект); + ОписаниеСоединения = Коннектор.ОписаниеСоединения(); + +КонецПроцедуры + +// Начинает событие запроса к СУБД и рассылает его наблюдателям. +// +// Параметры: +// ОперацияСУБД - Строка - SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, ROLLBACK. +// ИмяТаблицы - Строка - Таблица запроса; пустая строка, если таблицы нет. +// ТекстЗапроса - Строка, Неопределено - Текст запроса; Неопределено у коннекторов без SQL. +// +// Возвращаемое значение: +// СобытиеИсточникаДанных - Начатое событие. +// Неопределено - Наблюдателей нет, событие не создавалось. +// +Функция НачатьЗапрос(Знач ОперацияСУБД, Знач ИмяТаблицы = "", Знач ТекстЗапроса = Неопределено) Экспорт + + Событие = СоздатьСобытие(ВидыСобытийИсточникаДанных.Запрос(), ОперацияСУБД); + Если Событие = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Событие.УстановитьЗапрос(ИмяТаблицы, ТекстЗапроса); + Разослать("ПередСобытием", Событие); + + Возврат Событие; + +КонецФункции + +// Завершает событие запроса и рассылает его наблюдателям. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие из НачатьЗапрос. С Неопределено ничего не делает. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка, которой завершился запрос. +// КоличествоСтрок - Число, Неопределено - Число строк результата. +// +Процедура ЗавершитьЗапрос(Событие, Знач ИнформацияОбОшибке = Неопределено, Знач КоличествоСтрок = Неопределено) Экспорт + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Если КоличествоСтрок <> Неопределено Тогда + Событие.УстановитьКоличествоСтрок(КоличествоСтрок); + КонецЕсли; + + ЗавершитьСобытие(Событие, ИнформацияОбОшибке); + +КонецПроцедуры + +// Начинает событие операции и рассылает его наблюдателям. +// +// Параметры: +// Операция - Строка - Имя операции. +// ОбъектМодели - ОбъектМодели, Неопределено - Модель сущности операции. +// +// Возвращаемое значение: +// СобытиеИсточникаДанных - Начатое событие. +// Неопределено - Наблюдателей нет, событие не создавалось. +// +Функция НачатьОперацию(Знач Операция, Знач ОбъектМодели = Неопределено) Экспорт + + // Нужно ли событие, решает одна проверка - внутри СоздатьСобытие. Спроси мы отдельно, есть ли + // наблюдатели, между вопросом и ответом последнего могли бы снять: событие не создалось бы, + // а глубина уже была бы увеличена, и снять ее было бы некому - ЗавершитьОперацию без события + // ничего не делает. + Событие = СоздатьСобытие(ВидыСобытийИсточникаДанных.Операция(), Операция, ТекущаяГлубина()); + Если Событие = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + ИзменитьГлубину(1); + + Если ОбъектМодели <> Неопределено Тогда + Событие.УстановитьСущность(ОбъектМодели.ТипСущности(), ОбъектМодели.ИмяТаблицы()); + КонецЕсли; + + Разослать("ПередСобытием", Событие); + + Возврат Событие; + +КонецФункции + +// Завершает событие операции и рассылает его наблюдателям. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие из НачатьОперацию. С Неопределено ничего не делает. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка, которой завершилась операция. +// КоличествоСтрок - Число, Неопределено - Число прочитанных строк. +// +Процедура ЗавершитьОперацию(Событие, Знач ИнформацияОбОшибке = Неопределено, + Знач КоличествоСтрок = Неопределено) Экспорт + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + ИзменитьГлубину(-1); + + Если КоличествоСтрок <> Неопределено Тогда + Событие.УстановитьКоличествоСтрок(КоличествоСтрок); + КонецЕсли; + + ЗавершитьСобытие(Событие, ИнформацияОбОшибке); + +КонецПроцедуры + +// Создает событие любого вида, помеченное коннектором пула. +// +// Событие запоминает снимок наблюдателей на момент создания и обеим фазам рассылается ему: +// наблюдатель, снятый между фазами, все равно получает завершение начатого при нем события, +// а добавленный между фазами видит только события, начатые после регистрации. +// +// Параметры: +// Вид - Строка - Вид события. +// Операция - Строка - Операция события. +// Глубина - Число - Вложенность. +// НачалоСобытия - Число - Момент начала работы в миллисекундах, если она началась раньше, чем +// стало известно, нужно ли событие вообще. Неопределено - началом считается создание события. +// +// Возвращаемое значение: +// СобытиеИсточникаДанных - Событие, еще не разосланное. +// Неопределено - Наблюдателей нет. +// +Функция СоздатьСобытие(Знач Вид, Знач Операция, Знач Глубина = 0, Знач НачалоСобытия = Неопределено) Экспорт + + Если Пусто() Тогда + Возврат Неопределено; + КонецЕсли; + + Событие = Новый СобытиеИсточникаДанных(Вид, Операция, Глубина, НачалоСобытия); + Событие.УстановитьКоннектор(ТипКоннектора, ОписаниеСоединения); + Событие.УстановитьПолучателей(Наблюдатели); + + Возврат Событие; + +КонецФункции + +// Рассылает начало события. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие из СоздатьСобытие. +// +Процедура НачатьСобытие(Событие) Экспорт + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Разослать("ПередСобытием", Событие); + +КонецПроцедуры + +// Завершает событие и рассылает его завершение. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Начатое событие. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка, которой завершилось событие. +// +Процедура ЗавершитьСобытие(Событие, Знач ИнформацияОбОшибке = Неопределено) Экспорт + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + // Событие, начатое до открытия первого соединения (инициализация и ее захват), создавалось + // без описания от коннектора; к завершению описание уже известно. + Событие.УстановитьКоннектор(ТипКоннектора, ОписаниеСоединения); + Событие.Завершить(ИнформацияОбОшибке); + Разослать("ПослеСобытия", Событие); + +КонецПроцедуры + +// Откладывает рассылку событий текущего потока исполнения до РазослатьОтложенное. +// +// Нужно тому, кто держит блокировку: наблюдатель - чужой код, и звать его, не отпустив +// блокировку, нельзя. События, случившиеся под ней, дождутся своей рассылки в порядке +// возникновения, обе фазы каждого события в свой черед. +// +Процедура НачатьОтложеннуюРассылку() Экспорт + + Если ТекущийПоток().Данные.Получить(КлючОтложенных) <> Неопределено Тогда + Возврат; + КонецЕсли; + + ТекущийПоток().Данные.Вставить(КлючОтложенных, Новый Массив); + +КонецПроцедуры + +// Рассылает то, что накопилось с НачатьОтложеннуюРассылку, и выключает откладывание. +// +// Вызывается сразу после снятия блокировки. Без пары к НачатьОтложеннуюРассылку ничего не делает. +// +Процедура РазослатьОтложенное() Экспорт + + Отложенные = ТекущийПоток().Данные.Получить(КлючОтложенных); + Если Отложенные = Неопределено Тогда + Возврат; + КонецЕсли; + + ТекущийПоток().Данные.Удалить(КлючОтложенных); + + Для Каждого Рассылка Из Отложенные Цикл + Разослать(Рассылка.ИмяМетода, Рассылка.Событие); + КонецЦикла; + +КонецПроцедуры + +Процедура Разослать(Знач ИмяМетода, Событие) + + Отложенные = ТекущийПоток().Данные.Получить(КлючОтложенных); + Если Отложенные <> Неопределено Тогда + Отложенные.Добавить(Новый Структура("ИмяМетода, Событие", ИмяМетода, Событие)); + Возврат; + КонецЕсли; + + Для Каждого Наблюдатель Из Событие.Получатели() Цикл + + // Состояние события у каждого наблюдателя свое: событие должно знать, кому оно сейчас отдано. + Событие.УстановитьТекущегоНаблюдателя(Наблюдатель); + Попытка + Если ИмяМетода = "ПередСобытием" Тогда + Наблюдатель.ПередСобытием(Событие); + Иначе + Наблюдатель.ПослеСобытия(Событие); + КонецЕсли; + Исключение + Лог.Предупреждение( + "Наблюдатель %1 завершился ошибкой в %2 события %3 %4: %5", + ТипЗнч(Наблюдатель), + ИмяМетода, + Событие.Вид(), + Событие.Операция(), + ОписаниеОшибки() + ); + КонецПопытки; + + КонецЦикла; + + Событие.УстановитьТекущегоНаблюдателя(Неопределено); + +КонецПроцедуры + +Функция ТекущаяГлубина() + + Глубина = ТекущийПоток().Данные.Получить(КлючГлубины); + + Возврат ?(Глубина = Неопределено, 0, Глубина); + +КонецФункции + +Процедура ИзменитьГлубину(Знач Приращение) + ТекущийПоток().Данные.Вставить(КлючГлубины, Макс(ТекущаяГлубина() + Приращение, 0)); +КонецПроцедуры + +// Определяет, объявляет ли объект интерфейс аннотацией &Реализует на любом своем методе. +// +// Параметры: +// Объект - Произвольный - Проверяемый объект. +// ИмяИнтерфейса - Строка - Имя интерфейса. +// +// Возвращаемое значение: +// Булево - Истина, если объявление найдено. +// +Функция ОбъявляетИнтерфейс(Объект, Знач ИмяИнтерфейса) + + Рефлектор = Новый РефлекторОбъекта(Объект); + МетодыСАннотацией = Рефлектор.ПолучитьТаблицуМетодов("Реализует", Ложь); + + Для Каждого Метод Из МетодыСАннотацией Цикл + Аннотации = РаботаСАннотациями.НайтиАннотации(Метод.Аннотации, "Реализует"); + Для Каждого Аннотация Из Аннотации Цикл + ОбъектАннотации = ОпределениеАннотацииРеализует.СоздатьОбъектАннотации(Аннотация); + Если ОбъектАннотации.ИмяИнтерфейса() = ИмяИнтерфейса Тогда + Возврат Истина; + КонецЕсли; + КонецЦикла; + КонецЦикла; + + Возврат Ложь; + +КонецФункции + +Процедура ПроверитьОбъявлениеИнтерфейса(Наблюдатель) + + Если ОбъявляетИнтерфейс(Наблюдатель, "НаблюдательИсточникаДанных") Тогда + Возврат; + КонецЕсли; + + ТипНаблюдателя = ТипЗнч(Наблюдатель); + ВызватьИсключение СтрШаблон( + "Объект типа <%1> не объявляет интерфейс НаблюдательИсточникаДанных: " + + "нужна аннотация &Реализует(""НаблюдательИсточникаДанных"") на конструкторе", + ТипНаблюдателя + ); + +КонецПроцедуры + +Функция СистемаПоТипу(Знач ПТипКоннектора) + + ИмяТипа = Строка(ПТипКоннектора); + + Если ИмяТипа = "КоннекторPostgreSQL" Тогда + Возврат "postgresql"; + ИначеЕсли ИмяТипа = "КоннекторSQLite" Тогда + Возврат "sqlite"; + ИначеЕсли ИмяТипа = "КоннекторInMemory" Тогда + Возврат "inmemory"; + ИначеЕсли ИмяТипа = "КоннекторJSON" Тогда + Возврат "json"; + Иначе + Возврат НРег(ИмяТипа); + КонецЕсли; + +КонецФункции diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" index f8e9caa2..f7f01a16 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\203\320\273\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\221\320\224.os" @@ -15,6 +15,14 @@ // Перем НомерПоследнейЗаявки; +// Число - Сколько заявок в очереди еще чего-то ждут. +// +// Считать длину очереди нельзя: снятая заявка остается в ней до ближайшей уборки, а уборку делает +// только тот, кто очередь разбирает. Поток, уже получивший соединение, числился бы ожидающим - и +// это видели бы наблюдатели в снимке пула. +// +Перем ЖивыхЗаявок; + // Соответствие - Поток исполнения и выданное ему соединение. // Перем СоединенияПотоковИсполнения; @@ -74,6 +82,11 @@ // Перем Лог; +// НаблюдателиИсточникаДанных - Наблюдатели источника данных: получают события операций, +// запросов, соединений и транзакций, идущих через пул. +// +Перем Наблюдатели; + // Пул соединений с базой данных. // // Через пул идут все обращения к БД. Соединение выдается потоку исполнения на время операции @@ -150,6 +163,7 @@ ); НомерПоследнейЗаявки = 0; + ЖивыхЗаявок = 0; СоединенияПотоковИсполнения = Новый Соответствие(); ГлубиныЗахвата = Новый Соответствие(); Блокировка = Новый БлокировкаЧтенияЗаписи(); @@ -158,6 +172,51 @@ Закрыт = Ложь; ПаузаОжидания = ППаузаОжидания; ПределОжидания = ППределОжидания; + Наблюдатели = Новый НаблюдателиИсточникаДанных(ПТипКоннектора); + +КонецПроцедуры + +// Подменяет реестр наблюдателей пула реестром источника данных. +// +// Вызывается сразу после создания пула, пока ни одно соединение не открыто: коннекторы +// получают реестр при открытии, и подменять его позже нельзя. +// +// Параметры: +// ПНаблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей источника данных. +// +Процедура УстановитьНаблюдателей(Знач ПНаблюдатели) Экспорт + Наблюдатели = ПНаблюдатели; +КонецПроцедуры + +// Возвращает реестр наблюдателей пула. +// +// Возвращаемое значение: +// НаблюдателиИсточникаДанных - Наблюдатели источника данных, которому принадлежит пул. +// +Функция Наблюдатели() Экспорт + Возврат Наблюдатели; +КонецФункции + +// Захватывает блокировку записи, отложив рассылку событий. +// +// Под блокировкой события случаются - возврат соединения завершает брошенную транзакцию, а ее +// откат идет запросом к СУБД, - но рассылать их отсюда нельзя: наблюдатель это чужой код, и +// пока он работает, пул стоял бы целиком. Поэтому события копятся и уходят наблюдателям сразу +// после снятия блокировки, в РазблокироватьЗапись. +// +Процедура ЗаблокироватьЗапись() + + Наблюдатели.НачатьОтложеннуюРассылку(); + Блокировка.Запись().Заблокировать(); + +КонецПроцедуры + +// Снимает блокировку записи и рассылает накопившиеся события. +// +Процедура РазблокироватьЗапись() + + Блокировка.Запись().Разблокировать(); + Наблюдатели.РазослатьОтложенное(); КонецПроцедуры @@ -177,24 +236,37 @@ ПотокИсполнения = Потоки.Текущий(); - Блокировка.Запись().Заблокировать(); + // Возврат соединения за умершим потоком - такое же освобождение, как и явное, и начинается + // оно здесь: см. Освободить. + НачалоОсвобождения = ТекущаяУниверсальнаяДатаВМиллисекундах(); + + ЗаблокироватьЗапись(); + + ИтогОсвобождения = Конструктор_ИтогОсвобождения(); // Подписчиков у завершения потока столько, сколько пулов и источников его застали, и движок // обходит их одним циклом, не перехватывая ошибки обработчиков. Выпусти этот обработчик // исключение, и до подписавшихся позже очередь не дойдет: их соединения так и остались бы // за завершившимся потоком. Ошибка уходит в лог: поток кончился, и сообщать о ней некому. Попытка - ОсвободитьРесурсыПотокаИсполнения(ПотокИсполнения); - ОшибкаОсвобождения = ""; + ОсвободитьРесурсыПотокаИсполнения(ПотокИсполнения, ИтогОсвобождения); + ОшибкаОсвобождения = Неопределено; Исключение - ОшибкаОсвобождения = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + ОшибкаОсвобождения = ИнформацияОбОшибке(); КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); + + // Наблюдателям интересно возвращение соединения в пул, а чье оно было - живого потока или + // умершего - нет: без этого события его захват остался бы без пары. + ОпубликоватьОсвобождение(ИтогОсвобождения, НачалоОсвобождения, ОшибкаОсвобождения); // Запись в лог - вне блокировки: держать ее ради журнала незачем. - Если НЕ ПустаяСтрока(ОшибкаОсвобождения) Тогда - Лог.Ошибка("Не удалось освободить ресурсы завершившегося потока исполнения: %1", ОшибкаОсвобождения); + Если ОшибкаОсвобождения <> Неопределено Тогда + Лог.Ошибка( + "Не удалось освободить ресурсы завершившегося потока исполнения: %1", + ПодробноеПредставлениеОшибки(ОшибкаОсвобождения) + ); КонецЕсли; КонецПроцедуры @@ -220,7 +292,14 @@ // сообщит сам поток исполнения. Повторная подписка тем же обработчиком ничего не добавляет. ДобавитьОбработчик ПотокИсполнения.ПриЗавершении, ПриЗавершенииПотокаИсполнения; - Блокировка.Запись().Заблокировать(); + // Захват начинается здесь, а событие появится ниже: пока пул не отработал под блокировкой, + // неизвестно даже, нужно ли событие - углубление захвата его не порождает. Рассылать начало + // события из-под блокировки нельзя: наблюдатель - чужой код, а на блокировке стоит весь пул. + // Поэтому событие создается после, но с этим моментом начала: время, проведенное на + // блокировке, входит в захват соединения так же, как ожидание в очереди. + НачалоЗахвата = ТекущаяУниверсальнаяДатаВМиллисекундах(); + + ЗаблокироватьЗапись(); Попытка Итог = ПопытатьсяЗанять(ПотокИсполнения); @@ -228,21 +307,40 @@ Заявка = ВстатьВОчередь(ПотокИсполнения); КонецЕсли; Исключение - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); - Если Итог.Соединение <> Неопределено Тогда + // Углубление захвата соединения не меняет и наблюдателям не интересно. + Если Итог.Повторно Тогда Возврат Итог.Соединение; КонецЕсли; - Если Итог.Бронь Тогда - Возврат ОткрытьПоБрони(ПотокИсполнения); - КонецЕсли; + Событие = Наблюдатели.СоздатьСобытие(ВидыСобытийИсточникаДанных.Соединение(), "Занять", 0, НачалоЗахвата); + Наблюдатели.НачатьСобытие(Событие); + + ИтогЗахвата = Конструктор_ИтогЗахвата(); + + Попытка - Возврат ДождатьсяПоЗаявке(ПотокИсполнения, Заявка); + Если Итог.Соединение <> Неопределено Тогда + ИтогЗахвата.Соединение = Итог.Соединение; + ИначеЕсли Итог.Бронь Тогда + ОткрытьПоБрониСЗамером(ПотокИсполнения, ИтогЗахвата); + Иначе + ДождатьсяПоЗаявке(ПотокИсполнения, Заявка, ИтогЗахвата); + КонецЕсли; + + Исключение + ЗавершитьСобытиеСоединения(Событие, ИтогЗахвата, ИнформацияОбОшибке()); + ВызватьИсключение; + КонецПопытки; + + ЗавершитьСобытиеСоединения(Событие, ИтогЗахвата); + + Возврат ИтогЗахвата.Соединение; КонецФункции @@ -255,16 +353,30 @@ ПотокИсполнения = Потоки.Текущий(); - Блокировка.Запись().Заблокировать(); + // Как и захват, освобождение начинается до блокировки: под ней идет и возврат соединения, + // и закрытие непригодного - обращение к СУБД. Нужно ли событие, известно только после, + // поэтому оно создается ниже, но с этим моментом начала. + НачалоОсвобождения = ТекущаяУниверсальнаяДатаВМиллисекундах(); + + ЗаблокироватьЗапись(); + + ИтогОсвобождения = Конструктор_ИтогОсвобождения(); Попытка - ОсвободитьЗахват(ПотокИсполнения); + ОсвободитьЗахват(ПотокИсполнения, ИтогОсвобождения); Исключение - Блокировка.Запись().Разблокировать(); + ОшибкаОсвобождения = ИнформацияОбОшибке(); + РазблокироватьЗапись(); + // Соединение к этому моменту могло быть уже отобрано у потока: упасть возврат может и + // на закрытии непригодного соединения, когда пулу оно больше не принадлежит. + ОпубликоватьОсвобождение(ИтогОсвобождения, НачалоОсвобождения, ОшибкаОсвобождения); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); + + // Наблюдателям интересно возвращение соединения в пул, а не снятие одного уровня захвата. + ОпубликоватьОсвобождение(ИтогОсвобождения, НачалоОсвобождения); КонецПроцедуры @@ -361,7 +473,7 @@ Функция КоличествоОжидающих() Экспорт Блокировка.Чтение().Заблокировать(); - Результат = Ожидающие.Количество(); + Результат = ЖивыхЗаявок; Блокировка.Чтение().Разблокировать(); Возврат Результат; @@ -376,7 +488,7 @@ // Процедура Закрыть() Экспорт - Блокировка.Запись().Заблокировать(); + ЗаблокироватьЗапись(); Попытка @@ -412,7 +524,7 @@ Заявка.Соединение = Неопределено; СозданоСоединений = СозданоСоединений - 1; КонецЕсли; - Заявка.Снята = Истина; + СнятьЗаявку(Заявка); Ожидающие.Взять(); КонецЦикла; @@ -426,13 +538,13 @@ Исключение - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); КонецПроцедуры @@ -475,6 +587,7 @@ Если Соединение <> Неопределено Тогда ГлубиныЗахвата.Вставить(ПотокИсполнения, ГлубиныЗахвата.Получить(ПотокИсполнения) + 1); Итог.Соединение = Соединение; + Итог.Повторно = Истина; Возврат Итог; КонецЕсли; @@ -537,7 +650,7 @@ Функция ОткрытьПоБрони(Знач ПотокИсполнения) Попытка - Соединение = Новый СоединениеСБД(ТипКоннектора, СтрокаСоединения, ПараметрыКоннектора); + Соединение = Новый СоединениеСБД(ТипКоннектора, СтрокаСоединения, ПараметрыКоннектора, Наблюдатели); Исключение СнятьБронь(ПотокИсполнения); ВызватьИсключение; @@ -554,7 +667,7 @@ // Процедура СнятьБронь(Знач ПотокИсполнения) - Блокировка.Запись().Заблокировать(); + ЗаблокироватьЗапись(); Попытка Если Брони.Получить(ПотокИсполнения) <> Неопределено Тогда @@ -562,11 +675,11 @@ СозданоСоединений = СозданоСоединений - 1; КонецЕсли; Исключение - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); КонецПроцедуры @@ -586,7 +699,7 @@ // Функция ЗакрепитьОткрытое(Знач ПотокИсполнения, Соединение) - Блокировка.Запись().Заблокировать(); + ЗаблокироватьЗапись(); Попытка @@ -605,11 +718,11 @@ КонецЕсли; Исключение - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); Если ПулЗакрыт Тогда Соединение.Закрыть(); @@ -638,59 +751,88 @@ Заявка.Соединение = Неопределено; Заявка.Снята = Ложь; Ожидающие.Положить(Заявка); + ЖивыхЗаявок = ЖивыхЗаявок + 1; Возврат Заявка; КонецФункции +// Снимает заявку: она больше ничего не ждет. +// +// Из очереди заявка при этом не вынимается - вынуть ее оттуда можно только разбором всей очереди, +// а он и так случится при ближайшей уборке. Снятие идемпотентно: считать живые заявки можно только +// по переходу. +// +// Вызывается при захваченной блокировке записи. +// +// Параметры: +// Заявка - Структура - Заявка, поданная в очередь ожидающих. +// +Процедура СнятьЗаявку(Заявка) + + Если Заявка.Снята Тогда + Возврат; + КонецЕсли; + + Заявка.Снята = Истина; + ЖивыхЗаявок = ЖивыхЗаявок - 1; + +КонецПроцедуры + // Ждет, пока по заявке выдадут соединение. // // Параметры: // ПотокИсполнения - ПотокИсполнения - Поток, для которого выполняется операция. // Заявка - Структура - Заявка, поданная в очередь ожидающих. // -// Возвращаемое значение: -// СоединениеСБД - Соединение, закрепленное за потоком исполнения. +// ИтогЗахвата - Структура - Куда записать соединение, ожидание и сведения об открытии, +// см. Конструктор_ИтогЗахвата. // -Функция ДождатьсяПоЗаявке(Знач ПотокИсполнения, Заявка) +Процедура ДождатьсяПоЗаявке(Знач ПотокИсполнения, Заявка, ИтогЗахвата) - КрайнийСрок = ТекущаяУниверсальнаяДатаВМиллисекундах() + ПределОжидания; + НачалоОжидания = ТекущаяУниверсальнаяДатаВМиллисекундах(); + КрайнийСрок = НачалоОжидания + ПределОжидания; Пока Истина Цикл // Ждать нужно вне блокировки, иначе освободить соединение стало бы некому. Приостановить(ПаузаОжидания); - Блокировка.Запись().Заблокировать(); + ЗаблокироватьЗапись(); Попытка Итог = ПопытатьсяПолучитьПоЗаявке(Заявка, ПотокИсполнения); Исключение // Иначе выданное по брошенной заявке соединение не забрал бы никто. - Заявка.Снята = Истина; - Блокировка.Запись().Разблокировать(); + СнятьЗаявку(Заявка); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); Если Итог.Соединение <> Неопределено Тогда - Возврат Итог.Соединение; + ИтогЗахвата.Ожидание = ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоОжидания; + ИтогЗахвата.Соединение = Итог.Соединение; + Возврат; КонецЕсли; // Место освободилось, пока ждали: открываем свое соединение так же, как обычный захват - // без блокировки. Если Итог.Бронь Тогда - Возврат ОткрытьПоБрони(ПотокИсполнения); + ИтогЗахвата.Ожидание = ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоОжидания; + ОткрытьПоБрониСЗамером(ПотокИсполнения, ИтогЗахвата); + Возврат; КонецЕсли; Если ТекущаяУниверсальнаяДатаВМиллисекундах() >= КрайнийСрок Тогда + ИтогЗахвата.Ожидание = ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоОжидания; ОтказатьВОжидании(Заявка); КонецЕсли; КонецЦикла; -КонецФункции +КонецПроцедуры // Снимает заявку по истечении предела ожидания и завершает захват ошибкой. // @@ -705,11 +847,11 @@ // Процедура ОтказатьВОжидании(Заявка) - Блокировка.Запись().Заблокировать(); + ЗаблокироватьЗапись(); Попытка - Заявка.Снята = Истина; + СнятьЗаявку(Заявка); // Соединение могли назначить заявке в последний момент - между проверкой и этой строкой. // В снятой заявке забрать его больше некому. @@ -720,11 +862,11 @@ КонецЕсли; Исключение - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение; КонецПопытки; - Блокировка.Запись().Разблокировать(); + РазблокироватьЗапись(); ВызватьИсключение СтрШаблон( "Поток исполнения не дождался свободного соединения за %1 мс", @@ -750,7 +892,7 @@ Функция ПопытатьсяПолучитьПоЗаявке(Заявка, Знач ПотокИсполнения) Если Заявка.Соединение <> Неопределено Тогда - Заявка.Снята = Истина; + СнятьЗаявку(Заявка); ЗакрепитьЗаПотокомИсполнения(ПотокИсполнения, Заявка.Соединение); Итог = Конструктор_ИтогПопытки(); @@ -765,7 +907,7 @@ КонецЕсли; // Заявка больше не нужна: выданное по ней соединение осталось бы невостребованным. - Заявка.Снята = Истина; + СнятьЗаявку(Заявка); // Пока шла попытка, соединение могли назначить этой же заявке: завершившийся поток вернул // свое соединение в пул, а оттуда оно ушло в очередь ожидающих. В снятой заявке такое @@ -799,8 +941,9 @@ // // Параметры: // ПотокИсполнения - ПотокИсполнения - Поток, для которого выполняется операция. +// ИтогОсвобождения - Структура - См. Конструктор_ИтогОсвобождения. // -Процедура ОсвободитьЗахват(Знач ПотокИсполнения) +Процедура ОсвободитьЗахват(Знач ПотокИсполнения, ИтогОсвобождения) Глубина = ГлубиныЗахвата.Получить(ПотокИсполнения); Если Глубина = Неопределено Тогда @@ -822,7 +965,7 @@ Возврат; КонецЕсли; - ОтобратьСоединение(ПотокИсполнения); + ОтобратьСоединение(ПотокИсполнения, ИтогОсвобождения); КонецПроцедуры @@ -831,14 +974,20 @@ // // Параметры: // ПотокИсполнения - ПотокИсполнения - Поток, для которого выполняется операция. +// ИтогОсвобождения - Структура - См. Конструктор_ИтогОсвобождения. // -Процедура ОтобратьСоединение(Знач ПотокИсполнения) +Процедура ОтобратьСоединение(Знач ПотокИсполнения, ИтогОсвобождения) Соединение = СоединенияПотоковИсполнения.Получить(ПотокИсполнения); ГлубиныЗахвата.Удалить(ПотокИсполнения); СоединенияПотоковИсполнения.Удалить(ПотокИсполнения); + // Соединение у потока исполнения отобрано, и дальше оно ему не вернется: для наблюдателей + // освобождение состоялось. Отметка стоит до возврата в пул, потому что возврат закрывает + // непригодное соединение и вместе с закрытием может упасть. + ИтогОсвобождения.Возвращено = Истина; + ВернутьВПул(Соединение); КонецПроцедуры @@ -937,11 +1086,14 @@ // // Параметры: // ПотокИсполнения - ПотокИсполнения - Поток, чьи ресурсы освобождаются. +// ИтогОсвобождения - Структура - См. Конструктор_ИтогОсвобождения. Соединение, назначенное заявке +// такого потока, освобождением не считается: его захват не состоялся, и событие захвата, +// которому нужна пара, не создавалось. // -Процедура ОсвободитьРесурсыПотокаИсполнения(Знач ПотокИсполнения) +Процедура ОсвободитьРесурсыПотокаИсполнения(Знач ПотокИсполнения, ИтогОсвобождения) Если СоединенияПотоковИсполнения.Получить(ПотокИсполнения) <> Неопределено Тогда - ОтобратьСоединение(ПотокИсполнения); + ОтобратьСоединение(ПотокИсполнения, ИтогОсвобождения); КонецЕсли; // Поток, оборвавшийся посреди открытия соединения, свою бронь не снимет, а место в пуле @@ -986,7 +1138,7 @@ КонецЕсли; Если Заявка.ПотокИсполнения = ПотокИсполнения Тогда - Заявка.Снята = Истина; + СнятьЗаявку(Заявка); Брошенные.Добавить(Заявка); Иначе Ожидающие.Положить(Заявка); @@ -1018,7 +1170,132 @@ Итог = Новый Структура; Итог.Вставить("Соединение"); Итог.Вставить("Бронь", Ложь); + // Истина, если поток исполнения уже держал соединение и захват лишь углубился. + Итог.Вставить("Повторно", Ложь); Возврат Итог; КонецФункции + +// Сведения о том, как поток исполнения получил соединение: для события наблюдателей. +// +// Возвращаемое значение: +// Структура - Соединение, Открыто (открыто ли новое), ВремяОткрытия и Ожидание в миллисекундах. +// +Функция Конструктор_ИтогЗахвата() + + Итог = Новый Структура; + Итог.Вставить("Соединение"); + Итог.Вставить("Открыто", Ложь); + Итог.Вставить("ВремяОткрытия", 0); + Итог.Вставить("Ожидание", 0); + + Возврат Итог; + +КонецФункции + +// Сведения о том, отдал ли поток исполнения соединение пулу: для события наблюдателей. +// +// Итог отдельной структурой, а не возвращаемым значением, потому что возврат может завершиться +// ошибкой уже после того, как соединение отобрано у потока: закрытие непригодного соединения идет +// в СУБД и падает вместе с ней. Событие освобождения нужно и тогда - иначе событие захвата +// останется без пары, - а из результата, которого не будет, об этом не узнать. +// +// Возвращаемое значение: +// Структура - Возвращено: Истина, если соединение отобрано у потока исполнения. +// +Функция Конструктор_ИтогОсвобождения() + + Итог = Новый Структура; + Итог.Вставить("Возвращено", Ложь); + + Возврат Итог; + +КонецФункции + +// Открывает соединение по брони, замеряя время открытия. +// +// Параметры: +// ПотокИсполнения - ПотокИсполнения - Поток, для которого выполняется операция. +// ИтогЗахвата - Структура - Куда записать соединение и время открытия. +// +Процедура ОткрытьПоБрониСЗамером(Знач ПотокИсполнения, ИтогЗахвата) + + НачалоОткрытия = ТекущаяУниверсальнаяДатаВМиллисекундах(); + ИтогЗахвата.Соединение = ОткрытьПоБрони(ПотокИсполнения); + ИтогЗахвата.Открыто = Истина; + ИтогЗахвата.ВремяОткрытия = ТекущаяУниверсальнаяДатаВМиллисекундах() - НачалоОткрытия; + +КонецПроцедуры + +// Рассылает наблюдателям событие освобождения соединения, если оно состоялось. +// +// Зовется после снятия блокировки пула: снимок пула берется чтением, а наблюдатель - чужой код, +// которому под блокировкой работать нельзя. +// +// Параметры: +// ИтогОсвобождения - Структура - См. Конструктор_ИтогОсвобождения. +// НачалоОсвобождения - Число - Универсальная дата входа в пул в миллисекундах. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка возврата, обычно неудачного +// закрытия непригодного соединения. +// +Процедура ОпубликоватьОсвобождение( + ИтогОсвобождения, + Знач НачалоОсвобождения, + Знач ИнформацияОбОшибке = Неопределено +) + + Если НЕ ИтогОсвобождения.Возвращено Тогда + Возврат; + КонецЕсли; + + Событие = Наблюдатели.СоздатьСобытие( + ВидыСобытийИсточникаДанных.Соединение(), "Освободить", 0, НачалоОсвобождения); + Наблюдатели.НачатьСобытие(Событие); + ЗавершитьСобытиеСоединения(Событие, Неопределено, ИнформацияОбОшибке); + +КонецПроцедуры + +// Завершает событие захвата соединения снимком пула. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие захвата. +// ИтогЗахвата - Структура - См. Конструктор_ИтогЗахвата. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка захвата. +// +Процедура ЗавершитьСобытиеСоединения(Событие, ИтогЗахвата, Знач ИнформацияОбОшибке = Неопределено) + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Если ИтогЗахвата <> Неопределено Тогда + Событие.УстановитьДанныеЗахвата(ИтогЗахвата.Ожидание, ИтогЗахвата.Открыто, ИтогЗахвата.ВремяОткрытия); + КонецЕсли; + + Снимок = СнимокПула(); + Событие.УстановитьСнимокПула(Снимок.Занято, Снимок.Свободно, Снимок.Ожидает, Снимок.Предел); + Наблюдатели.ЗавершитьСобытие(Событие, ИнформацияОбОшибке); + +КонецПроцедуры + +// Снимает состояние пула одним чтением, чтобы значения снимка были согласованы между собой. +// +// Возвращаемое значение: +// Структура - Занято, Свободно, Ожидает, Предел: см. КоличествоЗанятых, КоличествоСвободных, +// КоличествоОжидающих и размер пула. +// +Функция СнимокПула() + + Снимок = Новый Структура; + + Блокировка.Чтение().Заблокировать(); + Снимок.Вставить("Занято", СоединенияПотоковИсполнения.Количество()); + Снимок.Вставить("Свободно", Свободные.Количество()); + Снимок.Вставить("Ожидает", ЖивыхЗаявок); + Снимок.Вставить("Предел", РазмерПула); + Блокировка.Чтение().Разблокировать(); + + Возврат Снимок; + +КонецФункции diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" index 0b93c0d1..4cbd711c 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\265\320\241\320\221\320\224.os" @@ -7,6 +7,11 @@ // Число - Момент последнего возврата в пул в миллисекундах. По нему пул выбирает, какое // соединение выдать следующим. Перем МоментВозврата; +// НаблюдателиИсточникаДанных - Наблюдатели пула: получают события транзакций этого соединения. +Перем НаблюдателиПула; +// Массив из СобытиеИсточникаДанных - Стопка событий транзакций: каждое начало кладет свое +// событие, каждое завершение снимает верхнее. Пуста вне транзакции и без наблюдателей. +Перем СобытияТранзакций; // Соединение с базой данных, выданное из пула соединений. // @@ -21,12 +26,25 @@ // ТипКоннектора - Тип - Тип класса, реализующего интерфейс коннектора. // СтрокаСоединения - Строка - Строка соединения с БД. // ПараметрыКоннектора - Массив - Дополнительные параметры коннектора. +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула: коннектор, объявивший +// НаблюдаемыйКоннектор, получает его после открытия. // -Процедура ПриСозданииОбъекта(Знач ТипКоннектора, Знач СтрокаСоединения, Знач ПараметрыКоннектора) +Процедура ПриСозданииОбъекта(Знач ТипКоннектора, Знач СтрокаСоединения, Знач ПараметрыКоннектора, Знач Наблюдатели) Коннектор = РаботаСКоннекторами.СоздатьКоннектор(ТипКоннектора); РаботаСКоннекторами.ОткрытьКоннектор(Коннектор, СтрокаСоединения, ПараметрыКоннектора); + // Коннектор уже открыт: ошибка подключения наблюдателей не должна оставить его без закрытия. + Попытка + Наблюдатели.ПодключитьКоннектор(Коннектор); + Исключение + РаботаСКоннекторами.ЗакрытьКоннектор(Коннектор); + ВызватьИсключение; + КонецПопытки; + + НаблюдателиПула = Наблюдатели; + + СобытияТранзакций = Новый Массив; ВТранзакции = Ложь; Испорчено = Ложь; ОтметитьВозврат(); @@ -49,7 +67,19 @@ // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакцию(Коннектор); + // Событие начинается до BEGIN и живет до исхода: фиксации, отмены, неудачи - в том числе + // самого начала - или отката брошенной транзакции. Свое событие получает каждое начало, + // в том числе вложенное: в трассировке вложенный вызов должен быть виден отдельным спаном. + НачатьСобытиеТранзакции(); + + Попытка + РаботаСКоннекторами.НачатьТранзакцию(Коннектор); + Исключение + // Неудачное начало закрывает только свое событие: начатая раньше транзакция жива. + ЗавершитьВерхнееСобытиеТранзакции("failed", ИнформацияОбОшибке()); + ВызватьИсключение; + КонецПопытки; + ВТранзакции = Истина; КонецПроцедуры @@ -63,10 +93,12 @@ РаботаСКоннекторами.ЗафиксироватьТранзакцию(Коннектор); Исключение ИспортитьПослеНеудачногоЗавершения(); + ЗавершитьВерхнееСобытиеТранзакции("failed", ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; ВТранзакции = Ложь; + ЗавершитьВерхнееСобытиеТранзакции("commit"); КонецПроцедуры @@ -79,10 +111,52 @@ РаботаСКоннекторами.ОтменитьТранзакцию(Коннектор); Исключение ИспортитьПослеНеудачногоЗавершения(); + ЗавершитьВерхнееСобытиеТранзакции("failed", ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; ВТранзакции = Ложь; + ЗавершитьВерхнееСобытиеТранзакции("rollback"); + +КонецПроцедуры + +// Начинает событие очередной транзакции и кладет его на вершину стопки. +// +// Без наблюдателей события нет: стопка остается пустой, завершать нечего. +// +Процедура НачатьСобытиеТранзакции() + + Событие = НаблюдателиПула.СоздатьСобытие(ВидыСобытийИсточникаДанных.Транзакция(), "Транзакция"); + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + СобытияТранзакций.Добавить(Событие); + НаблюдателиПула.НачатьСобытие(Событие); + +КонецПроцедуры + +// Снимает со стопки верхнее событие транзакции, завершает его исходом и отдает наблюдателям. +// +// Завершение всегда относится к последнему начатому событию: события транзакций вложены строго, +// как спаны в трассировке. +// +// Параметры: +// Исход - Строка - commit, rollback, failed или abandoned. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка завершения. +// +Процедура ЗавершитьВерхнееСобытиеТранзакции(Знач Исход, Знач ИнформацияОбОшибке = Неопределено) + + Если СобытияТранзакций.Количество() = 0 Тогда + Возврат; + КонецЕсли; + + Вершина = СобытияТранзакций.ВГраница(); + Событие = СобытияТранзакций[Вершина]; + СобытияТранзакций.Удалить(Вершина); + + Событие.УстановитьИсход(Исход); + НаблюдателиПула.ЗавершитьСобытие(Событие, ИнформацияОбОшибке); КонецПроцедуры @@ -137,13 +211,25 @@ Процедура ОтметитьВозврат() Экспорт Если ВТранзакции Тогда + // Транзакцию бросили: исход для наблюдателей - abandoned, каким бы ни был откат, поэтому + // откат идет мимо ОтменитьТранзакцию - та завершила бы событие исходом rollback. Попытка - ЭтотОбъект.ОтменитьТранзакцию(); + РаботаСКоннекторами.ОтменитьТранзакцию(Коннектор); + ОшибкаОтката = Неопределено; Исключение ИспортитьПослеНеудачногоЗавершения(); + ОшибкаОтката = ИнформацияОбОшибке(); КонецПопытки; + ВТранзакции = Ложь; + ЗавершитьВерхнееСобытиеТранзакции("abandoned", ОшибкаОтката); КонецЕсли; + // Остаток стопки - события начал, которым не досталось своего завершения: их закрывает + // возврат соединения, потому что дальше оно уйдет другому потоку исполнения. + Пока СобытияТранзакций.Количество() > 0 Цикл + ЗавершитьВерхнееСобытиеТранзакции("abandoned"); + КонецЦикла; + МоментВозврата = ТекущаяУниверсальнаяДатаВМиллисекундах(); КонецПроцедуры diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\236\320\277\320\265\321\200\320\260\321\206\320\270\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\236\320\277\320\265\321\200\320\260\321\206\320\270\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..a81f36d6 --- /dev/null +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\236\320\277\320\265\321\200\320\260\321\206\320\270\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,81 @@ +// Соединение под операцию источника данных: захват и возврат, парные событию операции. +// +// Обе половины протокола операции живут здесь: соединение берется под уже начатое событие и +// возвращается вместе с его завершением. Коннектора модуль не касается - он о пуле и наблюдателях. + +#Использовать logos + +// Лог - Журнал операций источника данных. +Перем Лог; + +// Занимает соединение для начатой операции. +// +// Захват может не удаться - пул закрыт или предел ожидания исчерпан. Такая операция должна +// завершиться для наблюдателей ошибкой, а не остаться начатой навсегда. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул, из которого берется соединение. +// Событие - СобытиеИсточникаДанных, Неопределено - Событие операции из НачатьОперацию. +// +// Возвращаемое значение: +// СоединениеСБД - Соединение текущего потока исполнения. +// +Функция ЗанятьДляОперации(Пул, Событие) Экспорт + + Попытка + Соединение = Пул.Занять(); + Исключение + Пул.Наблюдатели().ЗавершитьОперацию(Событие, ИнформацияОбОшибке()); + ВызватьИсключение; + КонецПопытки; + + Возврат Соединение; + +КонецФункции + +// Возвращает соединение операции в пул и завершает событие операции. +// +// Освобождение может не удаться: пул закрывает непригодное соединение, а закрытие идет в СУБД +// и падает вместе с ней. Событие операции завершается в любом случае - иначе наблюдатель не +// получит ПослеСобытия, а вложенность операций потока так и останется увеличенной. +// +// Ошибка освобождения идет наружу, только если операция прошла успешно. Упади они обе, наружу +// уходит ошибка операции: она первопричина, а неудачное освобождение - ее следствие. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул, которому возвращается соединение. +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// Событие - СобытиеИсточникаДанных, Неопределено - Событие операции из НачатьОперацию. +// ОшибкаОперации - ИнформацияОбОшибке, Неопределено - Ошибка, которой завершилась операция. +// КоличествоСтрок - Число, Неопределено - Число прочитанных операцией строк. +// +Процедура ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, + Знач ОшибкаОперации = Неопределено, Знач КоличествоСтрок = Неопределено) Экспорт + + Попытка + Пул.Освободить(); + Исключение + + Если ОшибкаОперации <> Неопределено Тогда + Лог.Ошибка( + "Не удалось освободить соединение после ошибки операции: %1", + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + Наблюдатели.ЗавершитьОперацию(Событие, ОшибкаОперации); + Возврат; + КонецЕсли; + + Наблюдатели.ЗавершитьОперацию(Событие, ИнформацияОбОшибке(), КоличествоСтрок); + ВызватьИсключение; + + КонецПопытки; + + Наблюдатели.ЗавершитьОперацию(Событие, ОшибкаОперации, КоличествоСтрок); + +КонецПроцедуры + +Процедура Инициализация() + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.operations"); +КонецПроцедуры + +Инициализация(); diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" index 17b0ec33..208d5b5e 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\274\320\270.os" @@ -31,20 +31,27 @@ ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); Пул = Менеджер.ИсточникДанных().ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию("Сохранить", ОбъектМодели); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка + Коннектор = Соединение.ПолучитьКоннектор(); Коннектор.Сохранить(ОбъектМодели, Сущность); СохранитьПодчиненныеТаблицы(Коннектор, ОбъектМодели, Сущность); + + // Пул сущностей обновляется до возврата соединения: экземпляр на идентификатор должен + // остаться один, а возврат идет уже после работы с СУБД и может упасть на закрытии + // непригодного соединения. + ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); + Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); - - ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие); КонецПроцедуры @@ -62,26 +69,50 @@ Функция Получить(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ОпцииПоиска); + + Возврат НайтиСущности(Менеджер, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска, "Получить"); + +КонецФункции + +// Ищет сущности соединением источника данных и отдает наблюдателям событие своей операции. +// +// Операция названа параметром: вызов ПолучитьОдно - такая же одна операция для наблюдателя, +// а не поиск, вложенный в поиск. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// ОпцииПоиска - ОпцииПоиска - Опции поиска в терминах колонок таблицы. +// ИмяОперации - Строка - Имя операции для наблюдателей. +// +// Возвращаемое значение: +// Массив - Найденные сущности. +// +Функция НайтиСущности(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска, Знач ИмяОперации) + НайденныеСущности = Новый Массив; Пул = Менеджер.ИсточникДанных().ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию(ИмяОперации, ОбъектМодели); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка Коннектор = Соединение.ПолучитьКоннектор(); - НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); + НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл НайденныеСущности.Добавить(СобратьСущность(Коннектор, Менеджер, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); КонецЦикла; Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, , НайденныеСущности.Количество()); Возврат НайденныеСущности; @@ -108,7 +139,9 @@ ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); КонецЕсли; - НайденныеСущности = Получить(Менеджер, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); + ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ПередаваемыеОпцииПоиска); + + НайденныеСущности = НайтиСущности(Менеджер, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска, "ПолучитьОдно"); Если НайденныеСущности.Количество() = 0 Тогда Возврат Неопределено; @@ -130,19 +163,24 @@ Процедура Удалить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт Пул = Менеджер.ИсточникДанных().ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию("Удалить", ОбъектМодели); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка + Соединение.ПолучитьКоннектор().Удалить(ОбъектМодели, Сущность); + + // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы + // ничего. Как и при сохранении, пул обновляется до возврата соединения. + ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); + Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); - - // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы ничего. - ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие); КонецПроцедуры @@ -162,16 +200,18 @@ Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниПроцедура()); Пул = Источник.ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию("ВыполнитьСКоннектором"); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка Действие.Выполнить(Соединение.ПолучитьКоннектор()); Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие); КонецПроцедуры @@ -195,16 +235,18 @@ Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниФункция()); Пул = Источник.ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию("ВычислитьСКоннектором"); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка Результат = Действие.Выполнить(Соединение.ПолучитьКоннектор()); Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие); Возврат Результат; diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\321\202\321\200\320\276\320\272\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\321\202\321\200\320\276\320\272\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217.os" new file mode 100644 index 00000000..b31d42ba --- /dev/null +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\241\321\202\321\200\320\276\320\272\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,164 @@ +// Разбор строк соединения вида "Ключ=Значение;Ключ=Значение". +// +// Нужен коннекторам, чтобы описать соединение наблюдателям: адрес, порт, база. Ключи +// сравниваются без учета регистра и пробелов вокруг; значения возвращаются как есть. + +// Разбирает строку соединения на пары. +// +// Параметры: +// СтрокаСоединения - Строка - Строка вида "Host=localhost;Port=5432;Database=test". +// +// Возвращаемое значение: +// Соответствие - Ключ в нижнем регистре без пробелов - значение без пробелов по краям. +// +Функция Параметры(Знач СтрокаСоединения) Экспорт + + Результат = Новый Соответствие; + + Для Каждого Пара Из СтрРазделить(СтрокаСоединения, ";", Ложь) Цикл + + ПозицияРавно = СтрНайти(Пара, "="); + Если ПозицияРавно = 0 Тогда + Продолжить; + КонецЕсли; + + Ключ = НРег(СокрЛП(Лев(Пара, ПозицияРавно - 1))); + Значение = СокрЛП(Сред(Пара, ПозицияРавно + 1)); + + Если ЗначениеЗаполнено(Ключ) Тогда + Результат.Вставить(Ключ, Значение); + КонецЕсли; + + КонецЦикла; + + Возврат Результат; + +КонецФункции + +// Возвращает первое найденное значение из перечисленных ключей. +// +// Параметры: +// ПараметрыСтроки - Соответствие - Результат Параметры(). +// Ключи - Строка - Ключи через запятую в порядке предпочтения, например "host,server". +// ЗначениеПоУмолчанию - Произвольный - Что вернуть, если ни одного ключа нет. +// +// Возвращаемое значение: +// Произвольный - Значение первого найденного ключа или значение по умолчанию. +// +Функция Значение(Знач ПараметрыСтроки, Знач Ключи, Знач ЗначениеПоУмолчанию = "") Экспорт + + Для Каждого Ключ Из СтрРазделить(Ключи, ",") Цикл + Найденное = ПараметрыСтроки.Получить(НРег(СокрЛП(Ключ))); + Если Найденное <> Неопределено Тогда + Возврат Найденное; + КонецЕсли; + КонецЦикла; + + Возврат ЗначениеПоУмолчанию; + +КонецФункции + +// Описание соединения без секретов для коннектора клиент-серверной СУБД. +// +// Параметры: +// Система - Строка - Имя СУБД. +// СтрокаСоединения - Строка - Строка соединения. +// ПортПоУмолчанию - Число - Порт, если в строке его нет. +// +// Возвращаемое значение: +// Структура - Система, Адрес, Порт, БазаДанных. +// +Функция ОписаниеСервера(Знач Система, Знач СтрокаСоединения, Знач ПортПоУмолчанию) Экспорт + + ПараметрыСтроки = Параметры(СтрокаСоединения); + + Порт = Значение(ПараметрыСтроки, "port", ""); + Порт = ?(ЭтоЦелоеЧисло(Порт), Число(Порт), ПортПоУмолчанию); + + Возврат Описание( + Система, + Значение(ПараметрыСтроки, "host,server,data source"), + Порт, + Значение(ПараметрыСтроки, "database,initial catalog") + ); + +КонецФункции + +// Описание соединения для файловой или встроенной СУБД без сервера. +// +// Параметры: +// Система - Строка - Имя СУБД. +// БазаДанных - Строка - Путь к файлу, каталогу или иное имя базы. +// +// Возвращаемое значение: +// Структура - Система, Адрес, Порт, БазаДанных. +// +Функция ОписаниеБезСервера(Знач Система, Знач БазаДанных) Экспорт + Возврат Описание(Система, "", 0, БазаДанных); +КонецФункции + +// Отрезает от URI параметры запроса: в них бывают учетные данные, которым в описании не место. +// +// Параметры: +// URI - Строка - URI или путь, например "file:base.db?cache=shared". +// +// Возвращаемое значение: +// Строка - Часть до первого "?", например "file:base.db"; строка без "?" возвращается как есть. +// +Функция БезПараметровЗапроса(Знач URI) Экспорт + + ПозицияВопроса = СтрНайти(URI, "?"); + Если ПозицияВопроса = 0 Тогда + Возврат URI; + КонецЕсли; + + Возврат Лев(URI, ПозицияВопроса - 1); + +КонецФункции + +// Собирает описание соединения из его полей. +// +// Параметры: +// Система - Строка - Имя СУБД. +// Адрес - Строка - Адрес сервера. +// Порт - Число - Порт сервера. +// БазаДанных - Строка - Имя базы, путь к файлу или каталогу. +// +// Возвращаемое значение: +// Структура - Система, Адрес, Порт, БазаДанных. +// +Функция Описание(Знач Система, Знач Адрес, Знач Порт, Знач БазаДанных) + + Результат = Новый Структура; + Результат.Вставить("Система", Система); + Результат.Вставить("Адрес", Адрес); + Результат.Вставить("Порт", Порт); + Результат.Вставить("БазаДанных", БазаДанных); + + Возврат Результат; + +КонецФункции + +// Проверяет, что текст состоит только из цифр: такой порт приводится к числу без исключения. +// +// Параметры: +// Текст - Строка - Значение параметра строки соединения. +// +// Возвращаемое значение: +// Булево - Истина, если текст непустой и состоит из одних цифр. +// +Функция ЭтоЦелоеЧисло(Знач Текст) + + Если ПустаяСтрока(Текст) Тогда + Возврат Ложь; + КонецЕсли; + + Для Номер = 1 По СтрДлина(Текст) Цикл + Если СтрНайти("0123456789", Сред(Текст, Номер, 1)) = 0 Тогда + Возврат Ложь; + КонецЕсли; + КонецЦикла; + + Возврат Истина; + +КонецФункции diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" index 8721767b..9fdf764a 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" @@ -4,6 +4,10 @@ // Конструктор объекта АбстрактныйКоннектор. // +// Класс описывает интерфейс коннектора: реализации объявляют его аннотацией +// &Реализует("АбстрактныйКоннектор") и могут дополнительно объявить НаблюдаемыйКоннектор. +// +&Интерфейс Процедура ПриСозданииОбъекта() Открыт = Ложь; КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" index 86b84b22..388e077e 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" @@ -3,18 +3,113 @@ Перем Лог; Перем Коннектор; +// НаблюдателиИсточникаДанных - Наблюдатели пула, которым сообщается о каждом запросе. Неопределено, +// пока коннектор работает вне пула. +Перем Наблюдатели; Процедура ПриСозданииОбъекта(пКоннектор, пЛог = Неопределено) - + Коннектор = пКоннектор; Если ЗначениеЗаполнено(пЛог) Тогда Лог = пЛог; - Иначе + Иначе Лог = Логирование.ПолучитьЛог("oscript.lib.entity.connector.abstractsql"); КонецЕсли; КонецПроцедуры +// Передает реестр наблюдателей, которым сообщается о запросах. +// +// Параметры: +// ПНаблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// +Процедура УстановитьНаблюдателей(ПНаблюдатели) Экспорт + Наблюдатели = ПНаблюдатели; +КонецПроцедуры + +// Выполняет подготовленный запрос, сообщая о нем наблюдателям. +// +// Текст и параметры запроса должны быть установлены до вызова. Событие запроса несет текст с +// плейсхолдерами параметров, число строк результата и ошибку, если запрос ею завершился. +// +// Параметры: +// Запрос - Запрос - Подготовленный запрос к СУБД. +// ОперацияСУБД - Строка - SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, ROLLBACK. +// ИмяТаблицы - Строка - Таблица запроса; пустая строка, если таблицы нет. +// ВернутьРезультат - Булево - Истина - выполнить как выборку и вернуть таблицу значений, +// Ложь - выполнить как команду. +// +// Возвращаемое значение: +// ТаблицаЗначений - Результат выборки. +// Неопределено - Запрос выполнен как команда. +// +Функция ВыполнитьСНаблюдением(Запрос, Знач ОперацияСУБД, Знач ИмяТаблицы, Знач ВернутьРезультат) Экспорт + + Событие = НачатьЗапрос(ОперацияСУБД, ИмяТаблицы, Запрос.Текст); + + Попытка + Если ВернутьРезультат Тогда + Результат = Запрос.Выполнить().Выгрузить(); + Иначе + Запрос.ВыполнитьКоманду(); + Результат = Неопределено; + КонецЕсли; + Исключение + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); + ВызватьИсключение; + КонецПопытки; + + ЗавершитьЗапрос(Событие, , ?(ВернутьРезультат, Результат.Количество(), Неопределено)); + + Возврат Результат; + +КонецФункции + +Функция НачатьЗапрос(Знач ОперацияСУБД, Знач ИмяТаблицы, Знач ТекстЗапроса) + + Если Наблюдатели = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Возврат Наблюдатели.НачатьЗапрос(ОперацияСУБД, ИмяТаблицы, ТекстЗапроса); + +КонецФункции + +Процедура ЗавершитьЗапрос(Событие, Знач ИнформацияОбОшибке = Неопределено, Знач КоличествоСтрок = Неопределено) + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Наблюдатели.ЗавершитьЗапрос(Событие, ИнформацияОбОшибке, КоличествоСтрок); + +КонецПроцедуры + +// Определяет операцию СУБД по первым словам текста запроса: SELECT, DELETE, CREATE TABLE и т.п. +// +// Параметры: +// ТекстЗапроса - Строка - Текст запроса или команды. +// +// Возвращаемое значение: +// Строка - Операция в верхнем регистре; для CREATE, DROP и ALTER вместе со вторым словом. +// +Функция ОперацияИзТекста(Знач ТекстЗапроса) + + Слова = СтрРазделить(СокрЛП(СтрЗаменить(ТекстЗапроса, Символы.ПС, " ")), " ", Ложь); + Если Слова.Количество() = 0 Тогда + Возврат ""; + КонецЕсли; + + Операция = ВРег(Слова[0]); + ОперацияНадСхемой = Операция = "CREATE" ИЛИ Операция = "DROP" ИЛИ Операция = "ALTER"; + Если ОперацияНадСхемой И Слова.Количество() > 1 Тогда + Операция = Операция + " " + ВРег(Слова[1]); + КонецЕсли; + + Возврат Операция; + +КонецФункции + // @Unstable // Выполнить произвольный запрос и получить результат. // @@ -33,8 +128,8 @@ Запрос = Коннектор.КонструкторКоннектора.НовыйЗапрос(Коннектор.Соединение); Запрос.Текст = ТекстЗапроса; - Результат = Запрос.Выполнить().Выгрузить(); - + Результат = ВыполнитьСНаблюдением(Запрос, ОперацияИзТекста(ТекстЗапроса), "", Истина); + Возврат Результат; КонецФункции @@ -64,9 +159,9 @@ КонецЕсли; Лог.Отладка("Удаление сущностей в таблице %1:%2%3", ОбъектМодели.ИмяТаблицы(), Символы.ПС, ТекстЗапроса); - + Запрос.Текст = ТекстЗапроса; - Запрос.ВыполнитьКоманду(); + ВыполнитьСНаблюдением(Запрос, "DELETE", ОбъектМодели.ИмяТаблицы(), Ложь); КонецПроцедуры @@ -121,8 +216,8 @@ Лог.Отладка("Поиск сущности в таблице %1:%2%3", ОбъектМодели.ИмяТаблицы(), Символы.ПС, ТекстЗапроса); Запрос.Текст = ТекстЗапроса; - Результат = Запрос.Выполнить().Выгрузить(); - + Результат = ВыполнитьСНаблюдением(Запрос, "SELECT", ОбъектМодели.ИмяТаблицы(), Истина); + Если Результат.Количество() = 0 Тогда Лог.Отладка("Сущность с типом %1 не найдена", ОбъектМодели.ТипСущности()); Возврат НайденныеСтроки; @@ -170,7 +265,7 @@ Запрос = Коннектор.КонструкторКоннектора.НовыйЗапрос(Коннектор.Соединение); Запрос.Текст = ТекстЗапроса; Запрос.УстановитьПараметр("Идентификатор", ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); - Запрос.ВыполнитьКоманду(); + ВыполнитьСНаблюдением(Запрос, "DELETE", ИмяТаблицы, Ложь); КонецПроцедуры @@ -245,8 +340,8 @@ Запрос = Коннектор.КонструкторКоннектора.НовыйЗапрос(Коннектор.Соединение); Запрос.Текст = ТекстЗапроса; - - Запрос.ВыполнитьКоманду(); + + ВыполнитьСНаблюдением(Запрос, "CREATE TABLE", ИмяТаблицы, Ложь); КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" index 1a8aa907..4c316b6e 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -17,6 +17,9 @@ Перем пРазмерПула; // ПулСоединенийСБД - Пул соединений источника. Перем пПул; +// НаблюдателиИсточникаДанных - Реестр наблюдателей: общий у источника, его пула и всех менеджеров, +// созданных из него. +Перем пНаблюдатели; // АтомарноеБулево - Закрывать источник по завершению потока, в котором он создан. Атомарное: // сеттер зовут из одного потока, обработчик завершения читает в другом. Перем пАвтоЗакрытие; @@ -69,6 +72,9 @@ // проверяются при создании: тип без интерфейса АбстрактныйКоннектор и размер, не являющийся // неотрицательным числом, отвергаются с ошибкой. // +// Наблюдатели регистрируются на источнике: реестр общий у источника, его пула и всех менеджеров, +// созданных из источника. +// // Параметры: // Имя - Строка - Имя источника данных. // ТипКоннектора - Тип - Тип класса, реализующего интерфейс АбстрактныйКоннектор. @@ -107,7 +113,10 @@ пПараметрыКоннектора = Новый ФиксированныйМассив(ПараметрыКоннектора); КонецЕсли; + пНаблюдатели = Новый НаблюдателиИсточникаДанных(пТипКоннектора); + пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); + пПул.УстановитьНаблюдателей(пНаблюдатели); пАвтоЗакрытие = Новый АтомарноеБулево(Истина); пЗакрыт = Новый АтомарноеБулево(Ложь); @@ -136,6 +145,46 @@ КонецПроцедуры +// Регистрирует наблюдателя источника данных. +// +// Наблюдатель получает события всех операций, запросов, соединений и транзакций каждого +// менеджера, созданного из этого источника: самого менеджера, его хранилищ сущностей и +// Active Record. Реестр общий у источника и его менеджеров, поэтому регистрировать можно +// и после создания менеджера. См. интерфейс НаблюдательИсточникаДанных и класс +// СобытиеИсточникаДанных. +// +// Параметры: +// Наблюдатель - Произвольный - Объект с аннотацией &Реализует("НаблюдательИсточникаДанных") на конструкторе, +// реализующий этот интерфейс. Неполная реализация отвергается при регистрации. +// +// Пример: +// Источник.ДобавитьНаблюдателя(Новый МойНаблюдатель); +// +Процедура ДобавитьНаблюдателя(Наблюдатель) Экспорт + пНаблюдатели.Добавить(Наблюдатель); +КонецПроцедуры + +// Снимает наблюдателя источника данных с регистрации. +// +// Параметры: +// Наблюдатель - Произвольный - Ранее зарегистрированный наблюдатель. Незарегистрированный пропускается. +// +Процедура УдалитьНаблюдателя(Наблюдатель) Экспорт + пНаблюдатели.Удалить(Наблюдатель); +КонецПроцедуры + +// BSLLS:UsingServiceTag-off +// @internal +// BSLLS:UsingServiceTag-on +// Реестр наблюдателей источника: источник передает его своему пулу соединений. +// +// Возвращаемое значение: +// НаблюдателиИсточникаДанных - Реестр наблюдателей. +// +Функция Наблюдатели() Экспорт + Возврат пНаблюдатели; +КонецФункции + // Имя источника данных. // // Возвращаемое значение: diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" index 4744d15b..8b5c9d4b 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" @@ -11,14 +11,40 @@ Перем Блокировка; // Рефлектор - Читает и заполняет поля сущностей по именам колонок модели. Перем Рефлектор; +// НаблюдателиИсточникаДанных - Наблюдатели пула, которым сообщается о каждой операции с таблицами. +// Неопределено, пока коннектор работает вне пула. +Перем Наблюдатели; +// Строка - Строка подключения, с которой открыт коннектор. +Перем СтрокаСоединенияКоннектора; // Конструктор объекта АбстрактныйКоннектор. // +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") Процедура ПриСозданииОбъекта() Открыт = Ложь; Рефлектор = Новый Рефлектор; + СтрокаСоединенияКоннектора = ""; КонецПроцедуры +// Передает реестр наблюдателей, которым коннектор сообщает об операциях с таблицами. +// +// Параметры: +// ПНаблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// +Процедура УстановитьНаблюдателей(ПНаблюдатели) Экспорт + Наблюдатели = ПНаблюдатели; +КонецПроцедуры + +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Система "inmemory", пустой адрес, нулевой порт и БазаДанных - строка подключения. +// +Функция ОписаниеСоединения() Экспорт + Возврат СтрокиСоединения.ОписаниеБезСервера("inmemory", СтрокаСоединенияКоннектора); +КонецФункции + // Открыть соединение с БД. // // Параметры: @@ -26,6 +52,7 @@ // ПараметрыКоннектора - Массив - Дополнительные параметры инициализации коннектора. // Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт // BSLLS:UnusedParameters-off + СтрокаСоединенияКоннектора = СтрокаСоединения; КешТаблиц = ХранилищеВПамяти.КешТаблиц(СтрокаСоединения); СчетчикиИдентификаторов = ХранилищеВПамяти.СчетчикиИдентификаторов(СтрокаСоединения); Блокировка = ХранилищеВПамяти.Блокировка(СтрокаСоединения); @@ -73,16 +100,55 @@ // Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Событие = НачатьЗапрос("CREATE TABLE", ОбъектМодели); Блокировка.Запись().Заблокировать(); Попытка СоздатьТаблицу(ОбъектМодели); Исключение Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие); + +КонецПроцедуры + +// Начинает событие запроса для наблюдателей пула. +// +// Параметры: +// ОперацияСУБД - Строка - Операция события. +// ОбъектМодели - ОбъектМодели - Модель таблицы. +// +// Возвращаемое значение: +// СобытиеИсточникаДанных, Неопределено - Событие; Неопределено вне пула или без наблюдателей. +// +Функция НачатьЗапрос(Знач ОперацияСУБД, ОбъектМодели) + + Если Наблюдатели = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Возврат Наблюдатели.НачатьЗапрос(ОперацияСУБД, ОбъектМодели.ИмяТаблицы()); + +КонецФункции + +// Завершает событие запроса для наблюдателей пула. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие из НачатьЗапрос. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка операции. +// КоличествоСтрок - Число, Неопределено - Число прочитанных строк. +// +Процедура ЗавершитьЗапрос(Событие, Знач ИнформацияОбОшибке = Неопределено, Знач КоличествоСтрок = Неопределено) + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Наблюдатели.ЗавершитьЗапрос(Событие, ИнформацияОбОшибке, КоличествоСтрок); КонецПроцедуры @@ -115,16 +181,19 @@ // Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Событие = НачатьЗапрос("INSERT", ОбъектМодели); Блокировка.Запись().Заблокировать(); Попытка ЗаписатьСущность(ОбъектМодели, Сущность); Исключение Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры @@ -200,16 +269,19 @@ // Процедура Удалить(ОбъектМодели, Сущность) Экспорт + Событие = НачатьЗапрос("DELETE", ОбъектМодели); Блокировка.Запись().Заблокировать(); Попытка УдалитьСущность(ОбъектМодели, Сущность); Исключение Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры @@ -260,16 +332,19 @@ // Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Событие = НачатьЗапрос("SELECT", ОбъектМодели); Блокировка.Чтение().Заблокировать(); Попытка Результат = ПрочитатьСтрокиТаблицы(ОбъектМодели, ОпцииПоиска); Исключение Блокировка.Чтение().Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Чтение().Разблокировать(); + ЗавершитьЗапрос(Событие, , Результат.Количество()); Возврат Результат; @@ -406,16 +481,19 @@ // Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Событие = НачатьЗапрос("DELETE", ОбъектМодели); Блокировка.Запись().Заблокировать(); Попытка УдалитьНайденныеСтроки(ОбъектМодели, ОпцииПоиска); Исключение Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Запись().Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" index 85d1a796..a0498f6a 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" @@ -1,3 +1,4 @@ +#Использовать "../internal" #Использовать "../internal/concurrent" #Использовать asserts @@ -12,15 +13,76 @@ Перем Лог; +// НаблюдателиИсточникаДанных - Наблюдатели пула, которым сообщается о каждой операции с таблицами. +// Неопределено, пока коннектор работает вне пула. +Перем Наблюдатели; + // Конструктор объекта АбстрактныйКоннектор. // +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") Процедура ПриСозданииОбъекта() - + Открыт = Ложь; + БазовыйКаталог = ""; Лог = Логирование.ПолучитьЛог("oscript.lib.entity.connector.json"); КонецПроцедуры +// Передает реестр наблюдателей, которым коннектор сообщает об операциях с таблицами. +// +// Параметры: +// ПНаблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// +Процедура УстановитьНаблюдателей(ПНаблюдатели) Экспорт + Наблюдатели = ПНаблюдатели; +КонецПроцедуры + +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Система "json", пустой адрес, нулевой порт и БазаДанных - каталог файлов таблиц. +// +Функция ОписаниеСоединения() Экспорт + Возврат СтрокиСоединения.ОписаниеБезСервера("json", БазовыйКаталог); +КонецФункции + +// Начинает событие запроса для наблюдателей пула. +// +// Параметры: +// ОперацияСУБД - Строка - Операция события. +// ОбъектМодели - ОбъектМодели - Модель таблицы. +// +// Возвращаемое значение: +// СобытиеИсточникаДанных, Неопределено - Событие; Неопределено вне пула или без наблюдателей. +// +Функция НачатьЗапрос(Знач ОперацияСУБД, ОбъектМодели) + + Если Наблюдатели = Неопределено Тогда + Возврат Неопределено; + КонецЕсли; + + Возврат Наблюдатели.НачатьЗапрос(ОперацияСУБД, ОбъектМодели.ИмяТаблицы()); + +КонецФункции + +// Завершает событие запроса для наблюдателей пула. +// +// Параметры: +// Событие - СобытиеИсточникаДанных, Неопределено - Событие из НачатьЗапрос. +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка операции. +// КоличествоСтрок - Число, Неопределено - Число прочитанных строк. +// +Процедура ЗавершитьЗапрос(Событие, Знач ИнформацияОбОшибке = Неопределено, Знач КоличествоСтрок = Неопределено) + + Если Событие = Неопределено Тогда + Возврат; + КонецЕсли; + + Наблюдатели.ЗавершитьЗапрос(Событие, ИнформацияОбОшибке, КоличествоСтрок); + +КонецПроцедуры + // Открыть соединение с БД. // // Параметры: @@ -77,6 +139,7 @@ // Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Событие = НачатьЗапрос("CREATE TABLE", ОбъектМодели); ИмяФайла = ОбъединитьПути(БазовыйКаталог, ОбъектМодели.ИмяТаблицы()) + ".json"; Блокировка = БлокировкиЧтенияЗаписи.Получить(ИмяФайла).Запись(); Блокировка.Заблокировать(); @@ -92,12 +155,14 @@ Исключение Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры @@ -108,6 +173,7 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для сохранения в БД. // Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Событие = НачатьЗапрос("INSERT", ОбъектМодели); ИмяФайла = ОбъединитьПути(БазовыйКаталог, ОбъектМодели.ИмяТаблицы()) + ".json"; Блокировка = БлокировкиЧтенияЗаписи.Получить(ИмяФайла).Запись(); Блокировка.Заблокировать(); @@ -149,12 +215,14 @@ Исключение Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры @@ -165,7 +233,8 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для удаления из БД. // Процедура Удалить(ОбъектМодели, Сущность) Экспорт - + + Событие = НачатьЗапрос("DELETE", ОбъектМодели); ИмяФайла = ОбъединитьПути(БазовыйКаталог, ОбъектМодели.ИмяТаблицы()) + ".json"; Блокировка = БлокировкиЧтенияЗаписи.Получить(ИмяФайла).Запись(); Блокировка.Заблокировать(); @@ -192,12 +261,14 @@ Исключение Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры @@ -223,6 +294,32 @@ // Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Событие = НачатьЗапрос("SELECT", ОбъектМодели); + + Попытка + НайденныеСтроки = ПрочитатьСтрокиТаблицы(ОбъектМодели, ОпцииПоиска); + Исключение + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); + ВызватьИсключение; + КонецПопытки; + + ЗавершитьЗапрос(Событие, , НайденныеСтроки.Количество()); + + Возврат НайденныеСтроки; + +КонецФункции + +// Читает строки таблицы по отбору, см. НайтиСтрокиВТаблице. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// ОпцииПоиска - ОпцииПоиска - Опции поиска. +// +// Возвращаемое значение: +// Массив - Массив соответствий имя колонки - значение. +// +Функция ПрочитатьСтрокиТаблицы(ОбъектМодели, ОпцииПоиска) + НайденныеСтроки = Новый Массив; ИмяФайла = ОбъединитьПути(БазовыйКаталог, ОбъектМодели.ИмяТаблицы()) + ".json"; @@ -381,16 +478,17 @@ // В качестве "ПутьКДанным" указываются имена колонок. // Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт - + Лог.Отладка("Удаление сущностей в таблице %1", ОбъектМодели.ИмяТаблицы()); + Событие = НачатьЗапрос("DELETE", ОбъектМодели); ИмяФайла = ОбъединитьПути(БазовыйКаталог, ОбъектМодели.ИмяТаблицы()) + ".json"; Блокировка = БлокировкиЧтенияЗаписи.Получить(ИмяФайла).Запись(); Блокировка.Заблокировать(); Попытка - НайденныеСтроки = НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); + НайденныеСтроки = ПрочитатьСтрокиТаблицы(ОбъектМодели, ОпцииПоиска); Лог.Отладка("К удалению сущностей: %1 шт.", НайденныеСтроки.Количество()); @@ -410,12 +508,14 @@ Исключение Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; Блокировка.Разблокировать(); + ЗавершитьЗапрос(Событие); КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index dc899970..30e0b43c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -1,3 +1,5 @@ +#Использовать "../internal" + #Использовать logos #Использовать strings @@ -5,15 +7,20 @@ Перем КонструкторКоннектора Экспорт; Перем Соединение Экспорт; Перем КартаТипов; +// Строка - Строка соединения, с которой открыт коннектор; нужна для описания соединения. +Перем СтрокаСоединенияКоннектора; Перем Лог Экспорт; // Конструктор объекта КоннекторPostgreSQL. // +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") Процедура ПриСозданииОбъекта() Лог = Логирование.ПолучитьЛог("oscript.lib.entity.connector.postgresql"); КоннекторSQL = Новый АбстрактныйКоннекторSQL(ЭтотОбъект, Лог); + СтрокаСоединенияКоннектора = ""; КонструкторКоннектора = ПолучитьКонструкторКоннектора(); Соединение = КонструкторКоннектора.НовыйСоединение(); @@ -28,9 +35,37 @@ // ПараметрыКоннектора - Массив - Дополнительные параметры инициализации коннектора. // Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + СтрокаСоединенияКоннектора = СтрокаСоединения; КонструкторКоннектора.Открыть(Соединение, СтрокаСоединения); КонецПроцедуры +// Передает реестр наблюдателей, которым коннектор сообщает о запросах. +// +// Параметры: +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт + КоннекторSQL.УстановитьНаблюдателей(Наблюдатели); +КонецПроцедуры + +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Система "postgresql", Адрес и Порт сервера, БазаДанных из строки соединения. Без пароля. +// +Функция ОписаниеСоединения() Экспорт + Возврат СтрокиСоединения.ОписаниеСервера("postgresql", СтрокаСоединенияКоннектора, ПортПоУмолчанию()); +КонецФункции + +// Порт сервера PostgreSQL, если в строке соединения он не указан. +// +// Возвращаемое значение: +// Число - Стандартный порт PostgreSQL. +// +Функция ПортПоУмолчанию() + Возврат 5432; // BSLLS:MagicNumber-off +КонецФункции + // Закрыть соединение с БД. // Процедура Закрыть() Экспорт @@ -50,25 +85,31 @@ // Начинает новую транзакцию в БД. // Процедура НачатьТранзакцию() Экспорт - Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "START TRANSACTION;"; - Запрос.ВыполнитьКоманду(); + ВыполнитьКоманду("BEGIN", "START TRANSACTION;"); КонецПроцедуры // Фиксирует открытую транзакцию в БД. // Процедура ЗафиксироватьТранзакцию() Экспорт - Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "COMMIT;"; - Запрос.ВыполнитьКоманду(); + ВыполнитьКоманду("COMMIT", "COMMIT;"); КонецПроцедуры // Отменяет открытую транзакцию в БД. // Процедура ОтменитьТранзакцию() Экспорт + ВыполнитьКоманду("ROLLBACK", "ROLLBACK;"); +КонецПроцедуры + +// Выполняет команду без таблицы, сообщая о ней наблюдателям. +// +// Параметры: +// ОперацияСУБД - Строка - Операция для события запроса. +// ТекстКоманды - Строка - Текст команды. +// +Процедура ВыполнитьКоманду(Знач ОперацияСУБД, Знач ТекстКоманды) Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "ROLLBACK;"; - Запрос.ВыполнитьКоманду(); + Запрос.Текст = ТекстКоманды; + КоннекторSQL.ВыполнитьСНаблюдением(Запрос, ОперацияСУБД, "", Ложь); КонецПроцедуры @@ -206,7 +247,7 @@ // поэтому внешняя блокировка тут не нужна. Если СоздаватьНовыйИдентификатор Тогда - РезультатВставки = Запрос.Выполнить().Выгрузить(); + РезультатВставки = КоннекторSQL.ВыполнитьСНаблюдением(Запрос, "INSERT", ИмяТаблицы, Истина); ОбъектМодели.УстановитьЗначениеКолонкиВПоле( Сущность, @@ -215,7 +256,7 @@ ); Иначе - Запрос.ВыполнитьКоманду(); + КоннекторSQL.ВыполнитьСНаблюдением(Запрос, "INSERT", ИмяТаблицы, Ложь); КонецЕсли; КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index 729eda82..6a22b4bd 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -1,3 +1,5 @@ +#Использовать "../internal" + #Использовать asserts #Использовать logos #Использовать reflector @@ -7,15 +9,20 @@ Перем КонструкторКоннектора Экспорт; Перем Соединение Экспорт; Перем КартаТипов; +// Строка - Строка соединения, с которой открыт коннектор; нужна для описания соединения. +Перем СтрокаСоединенияКоннектора; Перем Лог; // Конструктор объекта КоннекторSQLite. // +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") Процедура ПриСозданииОбъекта() Лог = Логирование.ПолучитьЛог("oscript.lib.entity.connector.sqlite"); КоннекторSQL = Новый АбстрактныйКоннекторSQL(ЭтотОбъект, Лог); + СтрокаСоединенияКоннектора = ""; КонструкторКоннектора = ПолучитьКонструкторКоннектора(); Соединение = КонструкторКоннектора.НовыйСоединение(); @@ -30,9 +37,35 @@ // ПараметрыКоннектора - Массив - Дополнительные параметры инициализации коннектора. // Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + СтрокаСоединенияКоннектора = СтрокаСоединения; КонструкторКоннектора.Открыть(Соединение, СтрокаСоединения); КонецПроцедуры +// Передает реестр наблюдателей, которым коннектор сообщает о запросах. +// +// Параметры: +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. +// +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт + КоннекторSQL.УстановитьНаблюдателей(Наблюдатели); +КонецПроцедуры + +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Система "sqlite", пустой адрес, нулевой порт и БазаДанных - путь или URI файла базы +// без параметров запроса: в них бывают секреты. +// +Функция ОписаниеСоединения() Экспорт + + ПараметрыСтроки = СтрокиСоединения.Параметры(СтрокаСоединенияКоннектора); + БазаДанных = СтрокиСоединения.Значение( + ПараметрыСтроки, "fulluri,data source,datasource,uri,filename", СтрокаСоединенияКоннектора); + + Возврат СтрокиСоединения.ОписаниеБезСервера("sqlite", СтрокиСоединения.БезПараметровЗапроса(БазаДанных)); + +КонецФункции + // Закрыть соединение с БД. // Процедура Закрыть() Экспорт @@ -52,25 +85,31 @@ // Начинает новую транзакцию в БД. // Процедура НачатьТранзакцию() Экспорт - Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "BEGIN TRANSACTION;"; - Запрос.ВыполнитьКоманду(); + ВыполнитьКоманду("BEGIN", "BEGIN TRANSACTION;"); КонецПроцедуры // Фиксирует открытую транзакцию в БД. // Процедура ЗафиксироватьТранзакцию() Экспорт - Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "COMMIT TRANSACTION;"; - Запрос.ВыполнитьКоманду(); + ВыполнитьКоманду("COMMIT", "COMMIT TRANSACTION;"); КонецПроцедуры // Отменяет открытую транзакцию в БД. // Процедура ОтменитьТранзакцию() Экспорт + ВыполнитьКоманду("ROLLBACK", "ROLLBACK TRANSACTION;"); +КонецПроцедуры + +// Выполняет команду без таблицы, сообщая о ней наблюдателям. +// +// Параметры: +// ОперацияСУБД - Строка - Операция для события запроса. +// ТекстКоманды - Строка - Текст команды. +// +Процедура ВыполнитьКоманду(Знач ОперацияСУБД, Знач ТекстКоманды) Запрос = КонструкторКоннектора.НовыйЗапрос(Соединение); - Запрос.Текст = "ROLLBACK TRANSACTION;"; - Запрос.ВыполнитьКоманду(); + Запрос.Текст = ТекстКоманды; + КоннекторSQL.ВыполнитьСНаблюдением(Запрос, ОперацияСУБД, "", Ложь); КонецПроцедуры // Создает таблицу в БД по данным модели. @@ -145,7 +184,7 @@ Лог.Отладка("Сохранение сущности с типом %1:%2%3", ОбъектМодели.ТипСущности(), Символы.ПС, ТекстЗапроса); Запрос.Текст = ТекстЗапроса; - Запрос.ВыполнитьКоманду(); + КоннекторSQL.ВыполнитьСНаблюдением(Запрос, "INSERT", ИмяТаблицы, Ложь); // Идентификатор объекта перезаписывается только тогда, когда его выдала СУБД. Если он был // задан вызывающим кодом, объект уже несет верное значение. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index 5c5d7598..33777345 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -34,14 +34,15 @@ // Конструктор объекта МенеджерСущностей. // // Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного -// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его -// временем жизни управляет тот, кто источник создал. +// источника делят соединения, транзакции потоков и наблюдателей. Параметры соединения задает +// источник, а его временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); +// Источник.ДобавитьНаблюдателя(Новый МойНаблюдатель); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // Процедура ПриСозданииОбъекта(Знач ИсточникДанных) @@ -92,16 +93,18 @@ ПроверитьЧтоМенеджерНеЗакрыт(); Пул = Источник.ПулСоединений(); - Соединение = Пул.Занять(); + Наблюдатели = Пул.Наблюдатели(); + Событие = Наблюдатели.НачатьОперацию("Инициализировать"); + Соединение = ОперацииИсточникаДанных.ЗанятьДляОперации(Пул, Событие); Попытка СоздатьТаблицы(Соединение.ПолучитьКоннектор()); Исключение - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие, ИнформацияОбОшибке()); ВызватьИсключение; КонецПопытки; - Пул.Освободить(); + ОперацииИсточникаДанных.ОсвободитьИЗавершитьОперацию(Пул, Наблюдатели, Событие); КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" new file mode 100644 index 00000000..a6dd57d7 --- /dev/null +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" @@ -0,0 +1,41 @@ +// Интерфейс коннектора, умеющего сообщать наблюдателям источника данных о запросах к СУБД. +// +// Дополняет АбстрактныйКоннектор, а не заменяет его: коннектор без этого интерфейса работает +// как раньше, только наблюдатели не получают событий уровня запроса и описания соединения. +// Встроенные коннекторы объявляют оба интерфейса аннотацией &Реализует на конструкторе. +// +// Пул соединений после открытия коннектора передает ему реестр наблюдателей и запрашивает +// описание соединения. Описание никогда не содержит секретов: пароль из строки соединения +// в него не попадает. +// +// Пример: +// &Реализует("АбстрактныйКоннектор") +// &Реализует("НаблюдаемыйКоннектор") +// Процедура ПриСозданииОбъекта() +// КонецПроцедуры +// +&Интерфейс +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +// Передает коннектору реестр наблюдателей, которым он сообщает о запросах. +// +// Параметры: +// Наблюдатели - НаблюдателиИсточникаДанных - Реестр наблюдателей пула. Коннектор создает события через +// НачатьЗапрос и завершает через ЗавершитьЗапрос; при пустом реестре события не создаются. +// +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры + +// Описывает соединение для атрибутов событий. +// +// Возвращаемое значение: +// Структура - Описание соединения без секретов: +// * Система - Строка - СУБД: postgresql, sqlite, inmemory, json. +// * Адрес - Строка - Адрес сервера; пустая строка, если сервера нет. +// * Порт - Число - Порт сервера; 0, если сервера нет. +// * БазаДанных - Строка - Имя базы, путь к файлу или каталогу. +// +Функция ОписаниеСоединения() Экспорт + Возврат Неопределено; +КонецФункции diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..1877f373 --- /dev/null +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,51 @@ +// Интерфейс наблюдателя источника данных. +// +// Наблюдатель получает события источника данных, с которым работает менеджер сущностей: +// операции (Сохранить, Получить, ПолучитьОдно, Удалить, Инициализировать, ВыполнитьСКоннектором, +// ВычислитьСКоннектором), запросы к СУБД, захват и освобождение соединений, транзакции. +// Каждое событие приходит дважды: до выполнения и после, одним и тем же объектом +// СобытиеИсточникаДанных. +// +// Реализация объявляет интерфейс аннотацией &Реализует("НаблюдательИсточникаДанных") на конструкторе +// и регистрируется на источнике данных методом ДобавитьНаблюдателя; менеджер, созданный из +// источника, отдает ей события. Регистрация проверяет реализацию через ВалидаторРеализации +// библиотеки extends, поэтому неполная реализация отвергается сразу. +// +// Наблюдатель вызывается из разных потоков исполнения одновременно и не должен держать +// несинхронизированного общего состояния: все, что нужно между фазами, кладется в событие +// методом УстановитьСостояние. Исключение наблюдателя пишется в лог и операцию не прерывает. +// +// Пример: +// &Реализует("НаблюдательИсточникаДанных") +// Процедура ПриСозданииОбъекта() +// КонецПроцедуры +// +// Процедура ПередСобытием(Событие) Экспорт +// Событие.УстановитьСостояние(ТекущаяУниверсальнаяДатаВМиллисекундах()); +// КонецПроцедуры +// +// Процедура ПослеСобытия(Событие) Экспорт +// Сообщить(СтрШаблон("%1 %2: %3 мс", Событие.Вид(), Событие.Операция(), Событие.Длительность())); +// КонецПроцедуры +// +&Интерфейс +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +// Вызывается перед выполнением события. +// +// Параметры: +// Событие - СобытиеИсточникаДанных - Начатое событие. Поля результата обычно еще не заполнены; +// у события, рассылка которого отложена, заполнены и они: см. Наблюдатели.md, раздел про +// потоки исполнения. +// +Процедура ПередСобытием(Событие) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры + +// Вызывается после выполнения события, в том числе завершившегося ошибкой. +// +// Параметры: +// Событие - СобытиеИсточникаДанных - Завершенное событие: заполнены Длительность, Ошибка и поля результата. +// +Процедура ПослеСобытия(Событие) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..8d5fbdad --- /dev/null +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,474 @@ +// Событие источника данных, передаваемое наблюдателям. +// +// Один и тот же объект приходит наблюдателю дважды: в ПередСобытием, когда заполнены только +// описательные поля, и в ПослеСобытия, когда заполнены длительность, ошибка и поля результата. +// Поля читаются функциями; менять их наблюдателю не следует. Для своих данных между фазами +// у наблюдателя есть Состояние - у каждого наблюдателя свое, чужого он не видит. +// +// Виды событий и заполняемые поля: +// * Операция - операция менеджера или хранилища: Операция, ТипСущности, ИмяТаблицы, Глубина; +// после - КоличествоСтрок для чтения. +// * Запрос - запрос к СУБД: Операция (SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, +// ROLLBACK), ИмяТаблицы, ТекстЗапроса (Неопределено у коннекторов без SQL); после - КоличествоСтрок. +// * Соединение - захват и освобождение соединения пула: Операция (Занять, Освободить); после - +// Ожидание, Открыто, ВремяОткрытия и снимок пула: Занято, Свободно, Ожидает, Предел. +// * Транзакция - транзакция потока исполнения: после - Исход (commit, rollback, failed, abandoned). +// +// Методы с пометкой @internal заполняют событие изнутри библиотеки. + +// BSLLS:UsingServiceTag-off + +// Строка - Вид события, значение перечисления ВидыСобытийИсточникаДанных. +Перем Вид; +// Строка - Операция события, см. описание класса. +Перем Операция; +// Число - Вложенность операции: 0 у вызванной из прикладного кода, 1 у порожденной ею и т.д. +Перем Глубина; +// Тип - Тип сущности операции. +Перем ТипСущности; +// Строка - Имя таблицы сущности или запроса. +Перем ИмяТаблицы; +// Строка - Текст запроса к СУБД. +Перем ТекстЗапроса; +// Число - Число строк результата чтения. +Перем КоличествоСтрок; +// Тип - Тип коннектора пула. +Перем ТипКоннектора; +// Строка - Система управления базой данных: postgresql, sqlite, inmemory, json. +Перем Система; +// Строка - Адрес сервера СУБД. +Перем Адрес; +// Число - Порт сервера СУБД. +Перем Порт; +// Строка - Имя базы данных или путь к файлу. +Перем БазаДанных; +// Число - Момент начала события, универсальная дата в миллисекундах. +Перем Начало; +// Число - Длительность события в миллисекундах. +Перем Длительность; +// ИнформацияОбОшибке - Ошибка, которой завершилось событие. +Перем Ошибка; +// Соответствие - Данные наблюдателей между фазами: наблюдатель -> что он положил. +Перем СостоянияНаблюдателей; +// НаблюдательИсточникаДанных, Неопределено - Наблюдатель, которому событие рассылается сейчас. +Перем ТекущийНаблюдатель; +// ФиксированныйМассив из НаблюдательИсточникаДанных - Получатели события: снимок реестра на момент +// создания, общий для обеих фаз. +Перем Получатели; +// Строка - Исход транзакции: commit, rollback, failed, abandoned. +Перем Исход; +// Число - Время ожидания соединения в очереди пула, в миллисекундах. +Перем Ожидание; +// Булево - Признак того, что при захвате открыто новое соединение. +Перем Открыто; +// Число - Время открытия нового соединения, в миллисекундах. +Перем ВремяОткрытия; +// Число - Соединений, занятых потоками исполнения, после события. +Перем Занято; +// Число - Свободных соединений в пуле после события. +Перем Свободно; +// Число - Потоков исполнения в очереди за соединением после события. +Перем Ожидает; +// Число - Размер пула. +Перем Предел; + +// Создает начатое событие. +// +// Создается библиотекой, напрямую конструировать не следует. +// +// Параметры: +// ПВид - Строка - Вид события, значение перечисления ВидыСобытийИсточникаДанных. +// ПОперация - Строка - Операция события. +// ПГлубина - Число - Вложенность операции. +// ПНачало - Число - Момент начала события в миллисекундах, если работа началась раньше создания +// события. Неопределено - началом считается создание. +// +Процедура ПриСозданииОбъекта(Знач ПВид, Знач ПОперация, Знач ПГлубина = 0, Знач ПНачало = Неопределено) + + Вид = ПВид; + Операция = ПОперация; + Глубина = ПГлубина; + Начало = ?(ПНачало = Неопределено, ТекущаяУниверсальнаяДатаВМиллисекундах(), ПНачало); + Порт = 0; + Адрес = ""; + БазаДанных = ""; + Система = ""; + ИмяТаблицы = ""; + СостоянияНаблюдателей = Новый Соответствие; + Получатели = Новый ФиксированныйМассив(Новый Массив); + +КонецПроцедуры + +// Вид события. +// +// Возвращаемое значение: +// Строка - Значение перечисления ВидыСобытийИсточникаДанных: Операция, Запрос, Соединение или Транзакция. +// +Функция Вид() Экспорт + Возврат Вид; +КонецФункции + +// Операция события. +// +// Возвращаемое значение: +// Строка - Имя операции, см. описание класса. +// +Функция Операция() Экспорт + Возврат Операция; +КонецФункции + +// Вложенность операции. +// +// Возвращаемое значение: +// Число - 0 у операции из прикладного кода, 1 у порожденной ею разыменованием ссылки и т.д. +// +Функция Глубина() Экспорт + Возврат Глубина; +КонецФункции + +// Тип сущности операции. +// +// Возвращаемое значение: +// Тип - Тип сущности. +// Неопределено - Операция не привязана к сущности. +// +Функция ТипСущности() Экспорт + Возврат ТипСущности; +КонецФункции + +// Имя таблицы сущности или запроса. +// +// Возвращаемое значение: +// Строка - Имя таблицы; пустая строка, если таблицы нет. +// +Функция ИмяТаблицы() Экспорт + Возврат ИмяТаблицы; +КонецФункции + +// Текст запроса к СУБД. +// +// Возвращаемое значение: +// Строка - Текст запроса с плейсхолдерами параметров. +// Неопределено - Коннектор не формирует текст запроса. +// +Функция ТекстЗапроса() Экспорт + Возврат ТекстЗапроса; +КонецФункции + +// Число строк результата чтения. +// +// Возвращаемое значение: +// Число - Число строк. +// Неопределено - Событие не читает строки или еще не завершено. +// +Функция КоличествоСтрок() Экспорт + Возврат КоличествоСтрок; +КонецФункции + +// Тип коннектора пула. +// +// Возвращаемое значение: +// Тип - Тип класса коннектора. +// +Функция ТипКоннектора() Экспорт + Возврат ТипКоннектора; +КонецФункции + +// Система управления базой данных. +// +// Возвращаемое значение: +// Строка - postgresql, sqlite, inmemory, json или имя типа коннектора в нижнем регистре. +// +Функция Система() Экспорт + Возврат Система; +КонецФункции + +// Адрес сервера СУБД. +// +// Возвращаемое значение: +// Строка - Адрес; пустая строка, если у СУБД нет сервера. +// +Функция Адрес() Экспорт + Возврат Адрес; +КонецФункции + +// Порт сервера СУБД. +// +// Возвращаемое значение: +// Число - Порт; 0, если у СУБД нет сервера. +// +Функция Порт() Экспорт + Возврат Порт; +КонецФункции + +// Имя базы данных. +// +// Возвращаемое значение: +// Строка - Имя базы, путь к файлу или строка подключения без секретов. +// +Функция БазаДанных() Экспорт + Возврат БазаДанных; +КонецФункции + +// Момент начала события. +// +// Возвращаемое значение: +// Число - Универсальная дата в миллисекундах. +// +Функция Начало() Экспорт + Возврат Начало; +КонецФункции + +// Длительность события. +// +// Возвращаемое значение: +// Число - Миллисекунды от начала до завершения. +// Неопределено - Событие еще не завершено. +// +Функция Длительность() Экспорт + Возврат Длительность; +КонецФункции + +// Ошибка, которой завершилось событие. +// +// Возвращаемое значение: +// ИнформацияОбОшибке - Ошибка. +// Неопределено - Событие завершилось успешно или еще не завершено. +// +Функция Ошибка() Экспорт + Возврат Ошибка; +КонецФункции + +// Данные наблюдателя, сохраненные между фазами. +// +// У каждого наблюдателя свое состояние: возвращается то, что в ПередСобытием положил +// наблюдатель, которому событие рассылается сейчас. +// +// Возвращаемое значение: +// Произвольный - Что положил наблюдатель; Неопределено, если ничего. +// +Функция Состояние() Экспорт + Возврат СостоянияНаблюдателей.Получить(ТекущийНаблюдатель); +КонецФункции + +// Сохраняет данные наблюдателя до фазы ПослеСобытия. +// +// Состояние принадлежит наблюдателю, который его положил: другие наблюдатели того же +// события его не видят и не затирают. +// +// Параметры: +// Значение - Произвольный - Данные наблюдателя, например открытый спан. +// +Процедура УстановитьСостояние(Значение) Экспорт + СостоянияНаблюдателей.Вставить(ТекущийНаблюдатель, Значение); +КонецПроцедуры + +// @internal +// Отмечает наблюдателя, которому событие рассылается сейчас: Состояние читается и пишется для него. +// +// Параметры: +// Наблюдатель - НаблюдательИсточникаДанных, Неопределено - Наблюдатель; Неопределено - рассылка окончена. +// +Процедура УстановитьТекущегоНаблюдателя(Знач Наблюдатель) Экспорт + ТекущийНаблюдатель = Наблюдатель; +КонецПроцедуры + +// @internal +// Получатели события: наблюдатели, зарегистрированные на момент его создания. +// +// Возвращаемое значение: +// ФиксированныйМассив из НаблюдательИсточникаДанных - Кому событие рассылается в обеих фазах. +// +Функция Получатели() Экспорт + Возврат Получатели; +КонецФункции + +// @internal +// Фиксирует получателей события: обе фазы уходят одному и тому же снимку реестра. +// +// Параметры: +// ПНаблюдатели - ФиксированныйМассив из НаблюдательИсточникаДанных - Снимок реестра. +// +Процедура УстановитьПолучателей(Знач ПНаблюдатели) Экспорт + Получатели = ПНаблюдатели; +КонецПроцедуры + +// Исход транзакции. +// +// Возвращаемое значение: +// Строка - commit, rollback, failed или abandoned. +// Неопределено - Событие не о транзакции или еще не завершено. +// +Функция Исход() Экспорт + Возврат Исход; +КонецФункции + +// Время ожидания соединения в очереди пула. +// +// Возвращаемое значение: +// Число - Миллисекунды; 0, если соединение досталось сразу. +// Неопределено - Событие не о захвате соединения. +// +Функция Ожидание() Экспорт + Возврат Ожидание; +КонецФункции + +// Признак открытия нового соединения при захвате. +// +// Возвращаемое значение: +// Булево - Истина, если пул открыл новое соединение, а не выдал свободное. +// Неопределено - Событие не о захвате соединения. +// +Функция Открыто() Экспорт + Возврат Открыто; +КонецФункции + +// Время открытия нового соединения. +// +// Возвращаемое значение: +// Число - Миллисекунды; 0, если соединение не открывалось. +// Неопределено - Событие не о захвате соединения. +// +Функция ВремяОткрытия() Экспорт + Возврат ВремяОткрытия; +КонецФункции + +// Соединений, занятых потоками исполнения, после события. +// +// Возвращаемое значение: +// Число - Занятые соединения. +// Неопределено - Событие не о соединении. +// +Функция Занято() Экспорт + Возврат Занято; +КонецФункции + +// Свободных соединений в пуле после события. +// +// Возвращаемое значение: +// Число - Свободные соединения. +// Неопределено - Событие не о соединении. +// +Функция Свободно() Экспорт + Возврат Свободно; +КонецФункции + +// Потоков исполнения в очереди за соединением после события. +// +// Возвращаемое значение: +// Число - Ожидающие. +// Неопределено - Событие не о соединении. +// +Функция Ожидает() Экспорт + Возврат Ожидает; +КонецФункции + +// Размер пула. +// +// Возвращаемое значение: +// Число - Предел одновременно живущих соединений; 0 - без предела. +// Неопределено - Событие не о соединении. +// +Функция Предел() Экспорт + Возврат Предел; +КонецФункции + +// @internal +// Привязывает событие к сущности. +// +// Параметры: +// ПТипСущности - Тип - Тип сущности. +// ПИмяТаблицы - Строка - Имя таблицы. +// +Процедура УстановитьСущность(Знач ПТипСущности, Знач ПИмяТаблицы) Экспорт + ТипСущности = ПТипСущности; + ИмяТаблицы = ПИмяТаблицы; +КонецПроцедуры + +// @internal +// Заполняет запрос к СУБД. +// +// Параметры: +// ПИмяТаблицы - Строка - Имя таблицы запроса. +// ПТекстЗапроса - Строка, Неопределено - Текст запроса. +// +Процедура УстановитьЗапрос(Знач ПИмяТаблицы, Знач ПТекстЗапроса) Экспорт + ИмяТаблицы = ПИмяТаблицы; + ТекстЗапроса = ПТекстЗапроса; +КонецПроцедуры + +// @internal +// Заполняет сведения о коннекторе и соединении. +// +// Параметры: +// ПТипКоннектора - Тип - Тип коннектора. +// Описание - Структура - Система, Адрес, Порт, БазаДанных. +// +Процедура УстановитьКоннектор(Знач ПТипКоннектора, Знач Описание) Экспорт + ТипКоннектора = ПТипКоннектора; + Система = Описание.Система; + Адрес = Описание.Адрес; + Порт = Описание.Порт; + БазаДанных = Описание.БазаДанных; +КонецПроцедуры + +// @internal +// Заполняет число строк результата. +// +// Параметры: +// ПКоличествоСтрок - Число - Число строк. +// +Процедура УстановитьКоличествоСтрок(Знач ПКоличествоСтрок) Экспорт + КоличествоСтрок = ПКоличествоСтрок; +КонецПроцедуры + +// @internal +// Заполняет исход транзакции. +// +// Параметры: +// ПИсход - Строка - commit, rollback, failed или abandoned. +// +Процедура УстановитьИсход(Знач ПИсход) Экспорт + Исход = ПИсход; +КонецПроцедуры + +// @internal +// Заполняет сведения о захвате соединения. +// +// Параметры: +// ПОжидание - Число - Ожидание в очереди, мс. +// ПОткрыто - Булево - Открыто ли новое соединение. +// ПВремяОткрытия - Число - Время открытия, мс. +// +Процедура УстановитьДанныеЗахвата(Знач ПОжидание, Знач ПОткрыто, Знач ПВремяОткрытия) Экспорт + Ожидание = ПОжидание; + Открыто = ПОткрыто; + ВремяОткрытия = ПВремяОткрытия; +КонецПроцедуры + +// @internal +// Заполняет снимок пула после события. +// +// Параметры: +// ПЗанято - Число - Занятые соединения. +// ПСвободно - Число - Свободные соединения. +// ПОжидает - Число - Ожидающие потоки исполнения. +// ППредел - Число - Размер пула. +// +Процедура УстановитьСнимокПула(Знач ПЗанято, Знач ПСвободно, Знач ПОжидает, Знач ППредел) Экспорт + Занято = ПЗанято; + Свободно = ПСвободно; + Ожидает = ПОжидает; + Предел = ППредел; +КонецПроцедуры + +// @internal +// Завершает событие: фиксирует длительность и ошибку. +// +// Параметры: +// ИнформацияОбОшибке - ИнформацияОбОшибке, Неопределено - Ошибка, если событие ею завершилось. +// +Процедура Завершить(Знач ИнформацияОбОшибке = Неопределено) Экспорт + Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало; + Ошибка = ИнформацияОбОшибке; +КонецПроцедуры diff --git "a/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..2b06e2cb --- /dev/null +++ "b/src/\320\234\320\276\320\264\321\203\320\273\320\270/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,60 @@ +// Виды событий, которые источник данных отдает наблюдателям: их возвращает функция Вид события. +// +// Значения отдаются функциями, а не экспортными переменными, чтобы их нельзя было изменить +// извне. Наблюдатель сравнивает вид события с ними, а не со строковыми литералами, например +// с ВидыСобытийИсточникаДанных.Запрос(). + +// ФиксированныйМассив из Строка - Все виды в порядке объявления. +Перем Виды; + +// Операция менеджера или хранилища: Сохранить, Получить, Удалить и другие. +// +// Возвращаемое значение: +// Строка - Вид события операции. +// +Функция Операция() Экспорт + Возврат "Операция"; +КонецФункции + +// Запрос коннектора к СУБД: SELECT, INSERT, DELETE, CREATE TABLE, BEGIN, COMMIT, ROLLBACK. +// +// Возвращаемое значение: +// Строка - Вид события запроса. +// +Функция Запрос() Экспорт + Возврат "Запрос"; +КонецФункции + +// Захват и освобождение соединения пула: Занять, Освободить. +// +// Возвращаемое значение: +// Строка - Вид события соединения. +// +Функция Соединение() Экспорт + Возврат "Соединение"; +КонецФункции + +// Транзакция потока исполнения от начала до исхода. +// +// Возвращаемое значение: +// Строка - Вид события транзакции. +// +Функция Транзакция() Экспорт + Возврат "Транзакция"; +КонецФункции + +// Все виды событий в порядке объявления. +// +// Возвращаемое значение: +// ФиксированныйМассив из Строка - Операция, Запрос, Соединение, Транзакция. +// +Функция Виды() Экспорт + Возврат Виды; +КонецФункции + +ПереченьВидов = Новый Массив; +ПереченьВидов.Добавить(Операция()); +ПереченьВидов.Добавить(Запрос()); +ПереченьВидов.Добавить(Соединение()); +ПереченьВидов.Добавить(Транзакция()); +Виды = Новый ФиксированныйМассив(ПереченьВидов); diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" new file mode 100644 index 00000000..92937970 --- /dev/null +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,55 @@ +// Коннектор, реализующий только АбстрактныйКоннектор: делегирует работу КоннекторInMemory, +// но интерфейс НаблюдаемыйКоннектор не объявляет. Проверяет, что наблюдатели получают события +// операций и без событий запроса, а сам коннектор работает как раньше. + +// КоннекторInMemory - Настоящий коннектор, которому делегируются все вызовы. +Перем Внутренний; + +&Реализует("АбстрактныйКоннектор") +Процедура ПриСозданииОбъекта() + Внутренний = Новый КоннекторInMemory; +КонецПроцедуры + +Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + Внутренний.Открыть(СтрокаСоединения, ПараметрыКоннектора); +КонецПроцедуры + +Процедура Закрыть() Экспорт + Внутренний.Закрыть(); +КонецПроцедуры + +Функция Открыт() Экспорт + Возврат Внутренний.Открыт(); +КонецФункции + +Процедура НачатьТранзакцию() Экспорт + Внутренний.НачатьТранзакцию(); +КонецПроцедуры + +Процедура ЗафиксироватьТранзакцию() Экспорт + Внутренний.ЗафиксироватьТранзакцию(); +КонецПроцедуры + +Процедура ОтменитьТранзакцию() Экспорт + Внутренний.ОтменитьТранзакцию(); +КонецПроцедуры + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Внутренний.ИнициализироватьТаблицу(ОбъектМодели); +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Внутренний.Сохранить(ОбъектМодели, Сущность); +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + Внутренний.Удалить(ОбъектМодели, Сущность); +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Внутренний.НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Внутренний.УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" new file mode 100644 index 00000000..edae5cd0 --- /dev/null +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" @@ -0,0 +1,61 @@ +// Коннектор, объявивший НаблюдаемыйКоннектор, но не реализовавший его целиком: нет +// ОписаниеСоединения. Нужен для проверки, что валидатор отвергает такой коннектор при каждом +// открытии соединения, а не только при первом. +// +// АбстрактныйКоннектор реализован полностью: неполный не пережил бы даже конструктор источника. + +// КоннекторInMemory - Настоящий коннектор, которому делегируются все вызовы. +Перем Внутренний; + +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") +Процедура ПриСозданииОбъекта() + Внутренний = Новый КоннекторInMemory; +КонецПроцедуры + +Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + Внутренний.Открыть(СтрокаСоединения, ПараметрыКоннектора); +КонецПроцедуры + +Процедура Закрыть() Экспорт + Внутренний.Закрыть(); +КонецПроцедуры + +Функция Открыт() Экспорт + Возврат Внутренний.Открыт(); +КонецФункции + +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры + +Процедура НачатьТранзакцию() Экспорт + Внутренний.НачатьТранзакцию(); +КонецПроцедуры + +Процедура ЗафиксироватьТранзакцию() Экспорт + Внутренний.ЗафиксироватьТранзакцию(); +КонецПроцедуры + +Процедура ОтменитьТранзакцию() Экспорт + Внутренний.ОтменитьТранзакцию(); +КонецПроцедуры + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Внутренний.ИнициализироватьТаблицу(ОбъектМодели); +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Внутренний.Сохранить(ОбъектМодели, Сущность); +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + Внутренний.Удалить(ОбъектМодели, Сущность); +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Внутренний.НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Внутренний.УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" new file mode 100644 index 00000000..622daf55 --- /dev/null +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" @@ -0,0 +1,81 @@ +// Коннектор, объявивший НаблюдаемыйКоннектор, у которого ОписаниеСоединения завершается ошибкой. +// Проверяет, что открытый коннектор при ошибке подключения наблюдателей закрывается, а не течет. +// Признак закрытия кладется в данные текущего потока исполнения под ключом КлючЗакрыт. + +// КоннекторInMemory - Настоящий коннектор, которому делегируются все вызовы. +Перем Внутренний; + +&Реализует("АбстрактныйКоннектор") +&Реализует("НаблюдаемыйКоннектор") +Процедура ПриСозданииОбъекта() + Внутренний = Новый КоннекторInMemory; +КонецПроцедуры + +// Ключ данных потока исполнения, под которым лежит признак закрытия коннектора. +// +// Возвращаемое значение: +// Строка - Ключ. +// +Функция КлючЗакрыт() Экспорт + Возврат "КоннекторСЛоманымОписанием.Закрыт"; +КонецФункции + +Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + Внутренний.Открыть(СтрокаСоединения, ПараметрыКоннектора); + ТекущийПоток().Данные.Вставить(КлючЗакрыт(), Ложь); +КонецПроцедуры + +Процедура Закрыть() Экспорт + Внутренний.Закрыть(); + ТекущийПоток().Данные.Вставить(КлючЗакрыт(), Истина); +КонецПроцедуры + +Функция Открыт() Экспорт + Возврат Внутренний.Открыт(); +КонецФункции + +Процедура УстановитьНаблюдателей(Наблюдатели) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры + +// BSLLS:FunctionShouldHaveReturn-off +// Описывает соединение для атрибутов событий: в этой фикстуре всегда завершается ошибкой. +// +// Возвращаемое значение: +// Структура - Не возвращается никогда: вызов завершается исключением. +// +Функция ОписаниеСоединения() Экспорт + ВызватьИсключение "Описание соединения недоступно"; +КонецФункции +// BSLLS:FunctionShouldHaveReturn-on + +Процедура НачатьТранзакцию() Экспорт + Внутренний.НачатьТранзакцию(); +КонецПроцедуры + +Процедура ЗафиксироватьТранзакцию() Экспорт + Внутренний.ЗафиксироватьТранзакцию(); +КонецПроцедуры + +Процедура ОтменитьТранзакцию() Экспорт + Внутренний.ОтменитьТранзакцию(); +КонецПроцедуры + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Внутренний.ИнициализироватьТаблицу(ОбъектМодели); +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Внутренний.Сохранить(ОбъектМодели, Сущность); +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + Внутренний.Удалить(ОбъектМодели, Сущность); +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Внутренний.НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Внутренний.УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" new file mode 100644 index 00000000..dd71cc39 --- /dev/null +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" @@ -0,0 +1,126 @@ +// Коннектор для тестов событий транзакций: данные держит в КоннекторInMemory, а транзакции +// изображает сам, потому что InMemory их не поддерживает. +// +// Единственный параметр коннектора - НастройкиКоннектораТранзакций, см. описание того модуля. +// Без параметров коннектор ведет себя как безотказная база, терпящая и вложенное начало. + +// КоннекторInMemory - Коннектор, которому делегируются операции с данными. +Перем Внутренний; +// Булево - Признак того, что начало транзакции должно падать. +Перем ПадатьПриНачалеТранзакции; +// Булево - Признак того, что завершение транзакции должно падать. +Перем ПадатьПриЗавершенииТранзакции; +// Булево - Признак того, что начало транзакции поверх начатой должно отвергаться. +Перем ОтвергатьВложеннуюТранзакцию; +// Число - Пауза внутри закрытия соединения в миллисекундах. +Перем ПаузаЗакрытия; +// СигналСобытия - Сигнал, отмечаемый в начале закрытия; Неопределено - сигналить некому. +Перем СигналЗакрытия; +// ПланЗакрытийКоннектора - План падений закрытия; Неопределено - закрытие не падает. +Перем ПланЗакрытий; +// Булево - Признак начатой транзакции. Нужен только для отказа во вложенной. +Перем ВТранзакции; + +&Реализует("АбстрактныйКоннектор") +Процедура ПриСозданииОбъекта() + Внутренний = Новый КоннекторInMemory; + ПадатьПриНачалеТранзакции = Ложь; + ПадатьПриЗавершенииТранзакции = Ложь; + ОтвергатьВложеннуюТранзакцию = Ложь; + ПаузаЗакрытия = 0; + СигналЗакрытия = Неопределено; + ПланЗакрытий = Неопределено; + ВТранзакции = Ложь; +КонецПроцедуры + +Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт + + Если ПараметрыКоннектора.Количество() > 0 Тогда + Настройки = ПараметрыКоннектора[0]; + ПадатьПриНачалеТранзакции = Настройки.ПадатьПриНачалеТранзакции; + ПадатьПриЗавершенииТранзакции = Настройки.ПадатьПриЗавершенииТранзакции; + ОтвергатьВложеннуюТранзакцию = Настройки.ОтвергатьВложеннуюТранзакцию; + ПаузаЗакрытия = Настройки.ПаузаЗакрытия; + СигналЗакрытия = Настройки.СигналЗакрытия; + ПланЗакрытий = Настройки.ПланЗакрытий; + КонецЕсли; + + Внутренний.Открыть(СтрокаСоединения, ПараметрыКоннектора); + +КонецПроцедуры + +Процедура Закрыть() Экспорт + + // Отметка до паузы: пул закрывает соединение под своей блокировкой записи, и тесту важен + // именно момент, когда блокировка уже занята. + Если СигналЗакрытия <> Неопределено Тогда + СигналЗакрытия.Отметить(); + КонецЕсли; + + Если ПаузаЗакрытия > 0 Тогда + Приостановить(ПаузаЗакрытия); + КонецЕсли; + + Если ПланЗакрытий <> Неопределено И ПланЗакрытий.ЗакрытиеПадает() Тогда + ВызватьИсключение "Закрытие соединения не удалось"; + КонецЕсли; + + Внутренний.Закрыть(); + +КонецПроцедуры + +Функция Открыт() Экспорт + Возврат Внутренний.Открыт(); +КонецФункции + +Процедура НачатьТранзакцию() Экспорт + + Если ПадатьПриНачалеТранзакции Тогда + ВызватьИсключение "Начало транзакции не удалось"; + КонецЕсли; + + Если ВТранзакции И ОтвергатьВложеннуюТранзакцию Тогда + ВызватьИсключение "Вложенная транзакция не поддерживается"; + КонецЕсли; + + ВТранзакции = Истина; + +КонецПроцедуры + +Процедура ЗафиксироватьТранзакцию() Экспорт + ЗавершитьТранзакцию(); +КонецПроцедуры + +Процедура ОтменитьТранзакцию() Экспорт + ЗавершитьТранзакцию(); +КонецПроцедуры + +Процедура ЗавершитьТранзакцию() + + Если ПадатьПриЗавершенииТранзакции Тогда + ВызватьИсключение "Завершение транзакции не удалось"; + КонецЕсли; + + ВТранзакции = Ложь; + +КонецПроцедуры + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + Внутренний.ИнициализироватьТаблицу(ОбъектМодели); +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + Внутренний.Сохранить(ОбъектМодели, Сущность); +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + Внутренний.Удалить(ОбъектМодели, Сущность); +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Внутренний.НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Внутренний.УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска); +КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" index 163aeffb..6db6068a 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\243\320\277\321\200\320\260\320\262\320\273\321\217\320\265\320\274\320\276\320\265\320\236\321\202\320\272\321\200\321\213\321\202\320\270\320\265.os" @@ -11,6 +11,7 @@ // НастройкиТестовогоКоннектора - Настройки, с которыми соединение открыто. Перем Настройки; +&Реализует("АбстрактныйКоннектор") Процедура ПриСозданииОбъекта() Открыт = Ложь; Настройки = Неопределено; diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271.os" new file mode 100644 index 00000000..38d3e868 --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\227\320\260\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271.os" @@ -0,0 +1,83 @@ +// BSLLS:ExportVariables-off +// Наблюдатель для тестов: запоминает события обеих фаз в порядке поступления. +// +// Параметры конструктора: +// ПадатьВПередСобытием - Булево - Истина, если ПередСобытием должен завершаться ошибкой. +// Проверяет, что ошибка наблюдателя не ломает операцию. + +// Массив из Структура - Записи о событиях: Фаза ("До" или "После") и само Событие. +Перем Записи Экспорт; +// Булево - Признак того, что ПередСобытием должен падать. +Перем ПадатьВПередСобытием; +// БлокировкаЧтенияЗаписи - События приходят из фоновых заданий, пока тест читает записи. +Перем Блокировка; + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта(Знач ППадатьВПередСобытием = Ложь) + Записи = Новый Массив; + ПадатьВПередСобытием = ППадатьВПередСобытием; + Блокировка = Новый БлокировкаЧтенияЗаписи; +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт + Если ПадатьВПередСобытием Тогда + ВызватьИсключение "Наблюдатель сломан"; + КонецЕсли; + Записать("До", Событие); +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Записать("После", Событие); +КонецПроцедуры + +Процедура Записать(Знач Фаза, Знач Событие) + Блокировка.Запись().Заблокировать(); + Записи.Добавить(Новый Структура("Фаза, Событие", Фаза, Событие)); + Блокировка.Запись().Разблокировать(); +КонецПроцедуры + +// Возвращает завершенные события указанного вида. +// +// Параметры: +// Вид - Строка - Вид события, значение ВидыСобытийИсточникаДанных. +// Операция - Строка - Операция события; пустая строка - любая. +// +// Возвращаемое значение: +// Массив из СобытиеИсточникаДанных - События фазы "После" в порядке завершения. +// +Функция Завершенные(Знач Вид, Знач Операция = "") Экспорт + Возврат Отобрать("После", Вид, Операция); +КонецФункции + +// Возвращает начатые события указанного вида. +// +// Параметры: +// Вид - Строка - Вид события, значение ВидыСобытийИсточникаДанных. +// Операция - Строка - Операция события; пустая строка - любая. +// +// Возвращаемое значение: +// Массив из СобытиеИсточникаДанных - События фазы "До" в порядке начала. +// +Функция Начатые(Знач Вид, Знач Операция = "") Экспорт + Возврат Отобрать("До", Вид, Операция); +КонецФункции + +Функция Отобрать(Знач Фаза, Знач Вид, Знач Операция) + + Результат = Новый Массив; + + Блокировка.Чтение().Заблокировать(); + Для Каждого Запись Из Записи Цикл + Если Запись.Фаза <> Фаза ИЛИ Запись.Событие.Вид() <> Вид Тогда + Продолжить; + КонецЕсли; + Если ЗначениеЗаполнено(Операция) И Запись.Событие.Операция() <> Операция Тогда + Продолжить; + КонецЕсли; + Результат.Добавить(Запись.Событие); + КонецЦикла; + Блокировка.Чтение().Разблокировать(); + + Возврат Результат; + +КонецФункции diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\235\320\265\320\277\320\276\320\273\320\275\321\213\320\271.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\235\320\265\320\277\320\276\320\273\320\275\321\213\320\271.os" new file mode 100644 index 00000000..f90dffb1 --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\235\320\265\320\277\320\276\320\273\320\275\321\213\320\271.os" @@ -0,0 +1,9 @@ +// Наблюдатель, объявивший интерфейс, но не реализовавший его целиком: нет ПослеСобытия. +// Нужен для проверки, что регистрация падает сразу, а не на первом событии. + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт // BSLLS:UnusedParameters-off +КонецПроцедуры diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\237\320\265\321\200\320\265\320\277\320\276\320\264\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271\321\201\321\217.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\237\320\265\321\200\320\265\320\277\320\276\320\264\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271\321\201\321\217.os" new file mode 100644 index 00000000..dc722240 --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\237\320\265\321\200\320\265\320\277\320\276\320\264\320\277\320\270\321\201\321\213\320\262\320\260\321\216\321\211\320\270\320\271\321\201\321\217.os" @@ -0,0 +1,48 @@ +// BSLLS:ExportVariables-off +// Наблюдатель для тестов состава получателей: в первом же ПередСобытием меняет реестр +// источника данных - снимает себя или добавляет другого наблюдателя - и записывает события обеих фаз. +// +// Параметры конструктора: +// ПИсточник - ИсточникДанных - Источник, реестр наблюдателей которого меняется. +// ПДругой - Произвольный, Неопределено - Наблюдатель, которого добавить; Неопределено - снять себя. + +// Массив из СобытиеИсточникаДанных - События фазы "До" в порядке поступления. +Перем Начатые Экспорт; +// Массив из СобытиеИсточникаДанных - События фазы "После" в порядке поступления. +Перем Завершенные Экспорт; +// ИсточникДанных - Источник, реестр которого меняется. +Перем Источник; +// Произвольный, Неопределено - Кого добавить; Неопределено - снять себя. +Перем Другой; +// Булево - Реестр уже изменен: меняется он один раз, в первом событии. +Перем РеестрИзменен; + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта(Знач ПИсточник, Знач ПДругой = Неопределено) + Начатые = Новый Массив; + Завершенные = Новый Массив; + Источник = ПИсточник; + Другой = ПДругой; + РеестрИзменен = Ложь; +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт + + Начатые.Добавить(Событие); + + Если РеестрИзменен Тогда + Возврат; + КонецЕсли; + РеестрИзменен = Истина; + + Если Другой = Неопределено Тогда + Источник.УдалитьНаблюдателя(ЭтотОбъект); + Иначе + Источник.ДобавитьНаблюдателя(Другой); + КонецЕсли; + +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Завершенные.Добавить(Событие); +КонецПроцедуры diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\241\320\234\320\265\321\202\320\272\320\276\320\271.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\241\320\234\320\265\321\202\320\272\320\276\320\271.os" new file mode 100644 index 00000000..daecff7f --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\241\320\234\320\265\321\202\320\272\320\276\320\271.os" @@ -0,0 +1,26 @@ +// BSLLS:ExportVariables-off +// Наблюдатель для тестов состояния события: в ПередСобытием кладет в событие свою метку, +// в ПослеСобытия запоминает, что из события прочитал. Два таких наблюдателя на одном менеджере +// проверяют, что состояние у каждого свое. +// +// Параметры конструктора: +// Метка - Строка - Что наблюдатель кладет в состояние события. + +// Массив из Произвольный - Состояния, прочитанные в ПослеСобытия, в порядке событий. +Перем Увиденные Экспорт; +// Строка - Метка наблюдателя. +Перем Метка; + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта(Знач ПМетка) + Увиденные = Новый Массив; + Метка = ПМетка; +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт + Событие.УстановитьСостояние(Метка); +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + Увиденные.Добавить(Событие.Состояние()); +КонецПроцедуры diff --git "a/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\242\320\276\321\200\320\274\320\276\320\267\321\217\321\211\320\270\320\271.os" "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\242\320\276\321\200\320\274\320\276\320\267\321\217\321\211\320\270\320\271.os" new file mode 100644 index 00000000..bb5b284c --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\321\214\320\242\320\276\321\200\320\274\320\276\320\267\321\217\321\211\320\270\320\271.os" @@ -0,0 +1,73 @@ +// Наблюдатель, который задерживается в ПослеСобытия события заданного вида. +// +// Нужен там, где проверяется, что чужой код не зовется из-под блокировки пула: пока такой +// наблюдатель спит, другой поток должен свободно работать с пулом. +// +// BSLLS:ExportVariables-off + +// Массив из Структура - Записи о событиях: Фаза ("До" или "После") и само Событие. +Перем Записи Экспорт; +// Строка - Вид события, на завершении которого наблюдатель засыпает. +Перем ВидСобытия; +// Число - Пауза внутри ПослеСобытия в миллисекундах. +Перем Пауза; +// СигналСобытия - Сигнал, отмечаемый перед паузой: по нему тест узнает, что наблюдатель уснул. +Перем СигналПаузы; +// БлокировкаЧтенияЗаписи - События приходят из фоновых заданий, пока тест читает записи. +Перем Блокировка; + +&Реализует("НаблюдательИсточникаДанных") +Процедура ПриСозданииОбъекта(Знач ПВидСобытия, Знач ППауза, Знач ПСигналПаузы) + Записи = Новый Массив; + ВидСобытия = ПВидСобытия; + Пауза = ППауза; + СигналПаузы = ПСигналПаузы; + Блокировка = Новый БлокировкаЧтенияЗаписи; +КонецПроцедуры + +Процедура ПередСобытием(Событие) Экспорт + Записать("До", Событие); +КонецПроцедуры + +Процедура ПослеСобытия(Событие) Экспорт + + Записать("После", Событие); + + Если Событие.Вид() <> ВидСобытия Тогда + Возврат; + КонецЕсли; + + СигналПаузы.Отметить(); + Приостановить(Пауза); + +КонецПроцедуры + +Процедура Записать(Знач Фаза, Знач Событие) + Блокировка.Запись().Заблокировать(); + Записи.Добавить(Новый Структура("Фаза, Событие", Фаза, Событие)); + Блокировка.Запись().Разблокировать(); +КонецПроцедуры + +// Возвращает завершенные события указанного вида. +// +// Параметры: +// Вид - Строка - Вид события. +// +// Возвращаемое значение: +// Массив из СобытиеИсточникаДанных - Завершенные события этого вида в порядке поступления. +// +Функция Завершенные(Знач Вид) Экспорт + + Найденные = Новый Массив; + + Блокировка.Чтение().Заблокировать(); + Для Каждого Запись Из Записи Цикл + Если Запись.Фаза = "После" И Запись.Событие.Вид() = Вид Тогда + Найденные.Добавить(Запись.Событие); + КонецЕсли; + КонецЦикла; + Блокировка.Чтение().Разблокировать(); + + Возврат Найденные; + +КонецФункции diff --git "a/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271.os" "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271.os" new file mode 100644 index 00000000..cab35741 --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271.os" @@ -0,0 +1,51 @@ +// Настройки КоннекторТранзакцийДляТестов: что коннектор делает на начале и завершении транзакции. +// +// Пул конструирует коннекторы сам, поэтому настроить экземпляр снаружи нельзя - настройки приходят +// тем же путем, что и всегда, через параметры коннектора. Единственным элементом массива параметров +// идет этот объект: позиционный список флагов читать невозможно. +// +// Класс хранит только данные, поэтому поля открыты - как у НастройкиТестовогоКоннектора. +// +// BSLLS:ExportVariables-off + +// Булево - Истина, если начало транзакции должно завершаться ошибкой. +Перем ПадатьПриНачалеТранзакции Экспорт; +// Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. +Перем ПадатьПриЗавершенииТранзакции Экспорт; +// Булево - Истина, если начало транзакции поверх начатой должно отвергаться, как это делает +// СУБД без поддержки вложенных транзакций. +Перем ОтвергатьВложеннуюТранзакцию Экспорт; +// Число - Пауза внутри закрытия соединения в миллисекундах. Изображает медленное закрытие, +// которое пул делает под своей блокировкой записи. +Перем ПаузаЗакрытия Экспорт; +// СигналСобытия - Сигнал, который коннектор отмечает в начале закрытия. Задан - тест может +// дождаться момента, когда блокировка пула уже занята закрывающим потоком. +Перем СигналЗакрытия Экспорт; +// ПланЗакрытийКоннектора - План падений закрытия: с какого по счету закрытия падать и сколько раз. +// Задан - решает он; иначе закрытие не падает. +Перем ПланЗакрытий Экспорт; + +Процедура ПриСозданииОбъекта() + + ПадатьПриНачалеТранзакции = Ложь; + ПадатьПриЗавершенииТранзакции = Ложь; + ОтвергатьВложеннуюТранзакцию = Ложь; + ПаузаЗакрытия = 0; + СигналЗакрытия = Неопределено; + ПланЗакрытий = Неопределено; + +КонецПроцедуры + +// Собирает параметры коннектора для ИсточникДанных и ПулСоединенийСБД. +// +// Возвращаемое значение: +// Массив - Параметры коннектора с этими настройками единственным элементом. +// +Функция ПараметрыКоннектора() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(ЭтотОбъект); + + Возврат Параметры; + +КонецФункции diff --git "a/tests/fixtures/\320\241\320\270\320\263\320\275\320\260\320\273\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os" "b/tests/fixtures/\320\241\320\270\320\263\320\275\320\260\320\273\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os" new file mode 100644 index 00000000..00b1f3d5 --- /dev/null +++ "b/tests/fixtures/\320\241\320\270\320\263\320\275\320\260\320\273\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os" @@ -0,0 +1,31 @@ +// Сигнал о том, что проверяемый код дошел до нужного места. +// +// Тестам, где два потока должны встретиться в определенной точке, недостаточно ждать публикации +// события: между ней и интересным местом проходит еще какой-то путь. Такой тест ждет отметку, +// которую ставит сам код в нужной точке. +// +// Экземпляр приходит в фикстуры параметрами и общий у них с тестом, поэтому отметку, сделанную +// фоновым потоком, видит основной. +// + +// Булево - Признак того, что отметка поставлена. +Перем Отмечен; + +Процедура ПриСозданииОбъекта() + Отмечен = Ложь; +КонецПроцедуры + +// Ставит отметку. +// +Процедура Отметить() Экспорт + Отмечен = Истина; +КонецПроцедуры + +// Определяет, поставлена ли отметка. +// +// Возвращаемое значение: +// Булево - Истина, если код дошел до отмечаемого места. +// +Функция Отмечен() Экспорт + Возврат Отмечен; +КонецФункции diff --git "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" index 3e2b726b..e367e0a7 100644 --- "a/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" +++ "b/tests/utils/\320\234\320\276\320\264\321\203\320\273\320\270/\320\242\320\265\321\201\321\202\320\276\320\262\321\213\320\265\320\243\321\202\320\270\320\273\320\270\321\202\321\213.os" @@ -111,6 +111,25 @@ КонецФункции +// Закрывает источники, созданные тестом, и очищает их перечень. +// +// Тест, заменивший источник новым, оставил бы прежний с открытыми соединениями до сборки мусора, +// поэтому закрываются все созданные, а не только последний. Повторное закрытие источник переживает, +// так что закрытый тестом сам попадает сюда безболезненно. +// +// Параметры: +// Источники - Массив из ИсточникДанных - Перечень созданных тестом источников. +// +Процедура ЗакрытьИсточники(Источники) Экспорт + + Для Каждого Источник Из Источники Цикл + Источник.Закрыть(); + КонецЦикла; + + Источники.Очистить(); + +КонецПроцедуры + // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..236db331 --- /dev/null +++ "b/tests/\320\222\320\270\320\264\321\213\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,105 @@ +// Модуль-перечисление ВидыСобытийИсточникаДанных: значения совпадают с видами событий, которые +// источник данных отдает наблюдателям, поэтому наблюдатель сравнивает Вид() с перечислением, +// а не со строковыми литералами. +// +// BSLLS:MagicNumber-off + +#Использовать ".." +#Использовать "utils" + +&Тест +Процедура ПеречислениеСодержитВсеВидыВПорядкеОбъявления() Экспорт + + Виды = ВидыСобытийИсточникаДанных.Виды(); + + Ожидаем.Что(Виды, "Перечень нельзя изменить извне").ИмеетТип("ФиксированныйМассив"); + Ожидаем.Что(Виды).ИмеетДлину(4); + Ожидаем.Что(Виды[0]).Равно(ВидыСобытийИсточникаДанных.Операция()); + Ожидаем.Что(Виды[1]).Равно(ВидыСобытийИсточникаДанных.Запрос()); + Ожидаем.Что(Виды[2]).Равно(ВидыСобытийИсточникаДанных.Соединение()); + Ожидаем.Что(Виды[3]).Равно(ВидыСобытийИсточникаДанных.Транзакция()); + +КонецПроцедуры + +&Тест +Процедура ЗначенияПеречисленияСовпадаютСВидамиСобытийИсточникаДанных() Экспорт + + ПодключитьФикстуры(); + + // Операции, запросы и соединения - на InMemory; транзакции он не поддерживает, + // их изображает тестовый коннектор поверх него. + Наблюдатель = НаблюдательПослеРаботы(Тип("КоннекторInMemory"), "виды-событий"); + НаблюдательТранзакций = НаблюдательПослеРаботы(Тип("КоннекторТранзакцийДляТестов"), "виды-событий-транзакции"); + + Ожидаем.Что(Наблюдатель.Завершенные(ВидыСобытийИсточникаДанных.Операция(), "Сохранить")).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные(ВидыСобытийИсточникаДанных.Запрос(), "INSERT")).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные(ВидыСобытийИсточникаДанных.Соединение(), "Занять")).Не_().ИмеетДлину(0); + Ожидаем.Что(НаблюдательТранзакций.Завершенные(ВидыСобытийИсточникаДанных.Транзакция())).ИмеетДлину(1); + + // Каждое событие несет ровно одно из значений перечисления. + Виды = ВидыСобытийИсточникаДанных.Виды(); + Для Каждого Запись Из Наблюдатель.Записи Цикл + Ожидаем.Что(Виды.Найти(Запись.Событие.Вид()), Запись.Событие.Вид()).Не_().Равно(Неопределено); + КонецЦикла; + +КонецПроцедуры + +// Служебные + +// Прогоняет через менеджер на указанном коннекторе сохранение внутри транзакции и возвращает +// наблюдателя с записанными событиями. +// +// Параметры: +// ТипКоннектора - Тип - Тип коннектора тестовой базы. +// СтрокаСоединения - Строка - Строка соединения с тестовой базой. +// +// Возвращаемое значение: +// НаблюдательЗаписывающий - Наблюдатель с записанными событиями. +// +Функция НаблюдательПослеРаботы(Знач ТипКоннектора, Знач СтрокаСоединения) + + ХранилищеВПамяти.Очистить(СтрокаСоединения); + + Источник = Новый ИсточникДанных("виды", ТипКоннектора, СтрокаСоединения); + Наблюдатель = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Наблюдатель); + + МенеджерСущностей = Новый МенеджерСущностей(Источник); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + МенеджерСущностей.Инициализировать(); + + Если ТипКоннектора <> Тип("КоннекторInMemory") Тогда + МенеджерСущностей.НачатьТранзакцию(); + КонецЕсли; + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + Если ТипКоннектора <> Тип("КоннекторInMemory") Тогда + МенеджерСущностей.ЗафиксироватьТранзакцию(); + КонецЕсли; + МенеджерСущностей.Закрыть(); + + Возврат Наблюдатель; + +КонецФункции + +Функция НовыйПростойОбъект(Знач Идентификатор) + Объект = Новый ПростойОбъект; + Объект.Идентификатор = Строка(Идентификатор); + Объект.Поле = "поле " + Идентификатор; + Возврат Объект; +КонецФункции + +Процедура ПодключитьФикстуры() + + Фикстуры = Новый Массив; + Фикстуры.Добавить("ПростойОбъект"); + Фикстуры.Добавить("НаблюдательЗаписывающий"); + Фикстуры.Добавить("КоннекторТранзакцийДляТестов"); + + Для Каждого Фикстура Из Фикстуры Цикл + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", Фикстура + ".os"), + Фикстура + ); + КонецЦикла; + +КонецПроцедуры diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index 2efc042c..3e20a0c7 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -62,11 +62,23 @@ Процедура КоннекторSQLiteРеализуетИнтерфейсКоннектора() Экспорт ИнтерфейсОбъекта = Новый ИнтерфейсОбъекта(); ИнтерфейсОбъекта.ИзОбъекта(Тип("АбстрактныйКоннектор")); - + РефлекторОбъекта = Новый РефлекторОбъекта(Тип("КоннекторSQLite")); РефлекторОбъекта.РеализуетИнтерфейс(ИнтерфейсОбъекта, Истина); КонецПроцедуры +// Описание соединения - экспортный метод интерфейса НаблюдаемыйКоннектор, и позвать его могут +// раньше открытия. Отдавать в полях структуры Неопределено вместо пустой строки в таком случае +// незачем: поля описания - строковые. +&Тест +Процедура ОписаниеНеоткрытогоКоннектораБезНеопределено() Экспорт + + Описание = Новый КоннекторSQLite().ОписаниеСоединения(); + + Ожидаем.Что(Описание.БазаДанных, "База данных неоткрытого коннектора - пустая строка").Равно(""); + +КонецПроцедуры + &Тест Процедура Сохранить() Экспорт diff --git "a/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os" "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os" new file mode 100644 index 00000000..1ccfe9d3 --- /dev/null +++ "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os" @@ -0,0 +1,283 @@ +// Наблюдатели источника данных: события уровня запроса у SQL-коннекторов и описание соединения. +// +// База выбирается как в остальных наборах: SQLite, если разрешен, иначе PostgreSQL. +// У обоих коннекторов события запроса несут текст SQL с плейсхолдерами параметров. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off + +#Использовать ".." +#Использовать "utils" + +// Источник данных, из которого тесты создают менеджер. +Перем Источник; +// Менеджер, который тесты создают заново перед каждым тестом. +Перем МенеджерСущностей; +// Наблюдатель, зарегистрированный до инициализации менеджера. +Перем Наблюдатель; +// Тип коннектора тестовой БД. +Перем ТипКоннектораТестовойБазы; +// Строка соединения с тестовой БД. +Перем СтрокаСоединенияКоннектора; + +&ПередКаждым +Процедура ПередЗапускомТеста() Экспорт + + ЗапускатьТестыPostgres = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true"); + ЗапускатьТестыSQLite = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true"); + + ВыполнятьСбросТаблиц = Ложь; + Если ЗапускатьТестыSQLite = "true" Тогда + СтрокаСоединенияКоннектора = "FullUri=file::memory:?cache=shared"; + ТипКоннектораТестовойБазы = Тип("КоннекторSQLite"); + ИначеЕсли ЗапускатьТестыPostgres = "true" Тогда + СтрокаСоединенияКоннектора = СтрокаСоединенияPostgres(); + ТипКоннектораТестовойБазы = Тип("КоннекторPostgreSQL"); + ВыполнятьСбросТаблиц = Истина; + Иначе + ВызватьИсключение "Нет доступного коннектора для тестирования наблюдателей запросов"; + КонецЕсли; + + ПодключитьФикстуры(); + + Источник = Новый ИсточникДанных("запросы", ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + // Подготовка и тест идут в разных потоках: с автозакрытием источник умер бы до теста. + Источник.УстановитьАвтоЗакрытие(Ложь); + Наблюдатель = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Наблюдатель); + + МенеджерСущностей = Новый МенеджерСущностей(Источник); + + Если ВыполнятьСбросТаблиц Тогда + ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); + КонецЕсли; + + МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("Автор")); + + МенеджерСущностей.Инициализировать(); + +КонецПроцедуры + +&ПослеКаждого +Процедура ПослеЗапускаТеста() Экспорт + МенеджерСущностей.ВыполнитьСКоннектором("Коннектор -> Коннектор.ВыполнитьЗапрос(""DELETE FROM Авторы"")"); + МенеджерСущностей.Закрыть(); + Источник.Закрыть(); +КонецПроцедуры + +&Тест +Процедура ИнициализацияПорождаетСобытияСозданияТаблиц() Экспорт + + Создания = Наблюдатель.Завершенные("Запрос", "CREATE TABLE"); + + Ожидаем.Что(Создания, "По событию на каждую таблицу модели").ИмеетДлину(2); + Ожидаем.Что(Создания[1].ИмяТаблицы()).Равно("Авторы"); + Ожидаем.Что(Создания[1].ТекстЗапроса()).Содержит("CREATE TABLE IF NOT EXISTS Авторы"); + Ожидаем.Что(Создания[1].Ошибка()).Равно(Неопределено); + +КонецПроцедуры + +&Тест +Процедура СохранениеПорождаетСобытиеВставки() Экспорт + + МенеджерСущностей.Сохранить(НовыйАвтор("Пушкин")); + + Вставки = Наблюдатель.Завершенные("Запрос", "INSERT"); + + Ожидаем.Что(Вставки).ИмеетДлину(1); + Ожидаем.Что(Вставки[0].ИмяТаблицы()).Равно("Авторы"); + Ожидаем.Что(Вставки[0].ТекстЗапроса()).Содержит("INSERT"); + Ожидаем.Что(Вставки[0].ТекстЗапроса(), "Значения передаются параметрами, а не текстом").Не_().Содержит("Пушкин"); + Ожидаем.Что(Вставки[0].Длительность()).Не_().Меньше(0); + +КонецПроцедуры + +&Тест +Процедура ЧтениеПорождаетСобытиеВыборкиСЧисломСтрок() Экспорт + + МенеджерСущностей.Сохранить(НовыйАвтор("Пушкин")); + МенеджерСущностей.Сохранить(НовыйАвтор("Гоголь")); + + МенеджерСущностей.Получить(Тип("Автор")); + + Выборки = Наблюдатель.Завершенные("Запрос", "SELECT"); + + Ожидаем.Что(Выборки).ИмеетДлину(1); + Ожидаем.Что(Выборки[0].ИмяТаблицы()).Равно("Авторы"); + Ожидаем.Что(Выборки[0].ТекстЗапроса()).Содержит("SELECT * FROM Авторы"); + Ожидаем.Что(Выборки[0].КоличествоСтрок()).Равно(2); + + Операции = Наблюдатель.Завершенные("Операция", "Получить"); + Ожидаем.Что(Выборки[0].Начало(), "Запрос начинается не раньше операции").Не_().Меньше(Операции[0].Начало()); + +КонецПроцедуры + +&Тест +Процедура УдалениеПорождаетСобытиеУдаления() Экспорт + + Автор = НовыйАвтор("Пушкин"); + МенеджерСущностей.Сохранить(Автор); + МенеджерСущностей.Удалить(Автор); + + Удаления = Наблюдатель.Завершенные("Запрос", "DELETE"); + + Ожидаем.Что(Удаления).ИмеетДлину(1); + Ожидаем.Что(Удаления[0].ИмяТаблицы()).Равно("Авторы"); + Ожидаем.Что(Удаления[0].ТекстЗапроса()).Содержит("DELETE FROM Авторы"); + +КонецПроцедуры + +&Тест +Процедура ТранзакцияПорождаетСобытияКомандТранзакции() Экспорт + + МенеджерСущностей.НачатьТранзакцию(); + МенеджерСущностей.Сохранить(НовыйАвтор("Пушкин")); + МенеджерСущностей.ЗафиксироватьТранзакцию(); + + МенеджерСущностей.НачатьТранзакцию(); + МенеджерСущностей.Сохранить(НовыйАвтор("Гоголь")); + МенеджерСущностей.ОтменитьТранзакцию(); + + Ожидаем.Что(Наблюдатель.Завершенные("Запрос", "BEGIN")).ИмеетДлину(2); + Ожидаем.Что(Наблюдатель.Завершенные("Запрос", "COMMIT")).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные("Запрос", "ROLLBACK")).ИмеетДлину(1); + + Начало = Наблюдатель.Завершенные("Запрос", "BEGIN")[0]; + Ожидаем.Что(Начало.ТекстЗапроса()).Содержит("TRANSACTION"); + Ожидаем.Что(Начало.ИмяТаблицы()).Равно(""); + +КонецПроцедуры + +&Тест +Процедура ПроизвольныйЗапросПорождаетСобытиеСОперациейИзТекста() Экспорт + + Строки = МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> Коннектор.ВыполнитьЗапрос(""SELECT 1 AS Единица"")"); + + Ожидаем.Что(Строки).ИмеетДлину(1); + + Выборки = Наблюдатель.Завершенные("Запрос", "SELECT"); + Ожидаем.Что(Выборки).ИмеетДлину(1); + Ожидаем.Что(Выборки[0].ТекстЗапроса()).Равно("SELECT 1 AS Единица"); + Ожидаем.Что(Выборки[0].ИмяТаблицы()).Равно(""); + Ожидаем.Что(Выборки[0].КоличествоСтрок()).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ОшибкаЗапросаПопадаетВСобытиеИДоходитДоВызывающего() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить("Коннектор -> Коннектор.ВыполнитьЗапрос(""SELECT * FROM НетТакойТаблицы"")"); + + Ожидаем.Что(МенеджерСущностей) + .Метод("ВычислитьСКоннектором", Параметры) + .ВыбрасываетИсключение("НетТакойТаблицы"); + + Выборки = Наблюдатель.Завершенные("Запрос", "SELECT"); + Ожидаем.Что(Выборки).ИмеетДлину(1); + Ожидаем.Что(Выборки[0].Ошибка()).ИмеетТип("ИнформацияОбОшибке"); + Ожидаем.Что(Выборки[0].Длительность()).Не_().Меньше(0); + + Операции = Наблюдатель.Завершенные("Операция", "ВычислитьСКоннектором"); + Ожидаем.Что(Операции[0].Ошибка(), "Ошибка запроса поднимается и в операцию").ИмеетТип("ИнформацияОбОшибке"); + +КонецПроцедуры + +&Тест +Процедура СобытияНесутОписаниеСоединенияБезСекретов() Экспорт + + МенеджерСущностей.Сохранить(НовыйАвтор("Пушкин")); + + Событие = Наблюдатель.Завершенные("Запрос", "INSERT")[0]; + + Если ТипКоннектораТестовойБазы = Тип("КоннекторSQLite") Тогда + Ожидаем.Что(Событие.Система()).Равно("sqlite"); + Ожидаем.Что(Событие.БазаДанных(), "URI без параметров запроса: в них бывают секреты").Равно("file::memory:"); + Ожидаем.Что(Событие.Адрес()).Равно(""); + Ожидаем.Что(Событие.Порт()).Равно(0); + Иначе + Ожидаем.Что(Событие.Система()).Равно("postgresql"); + Ожидаем.Что(Событие.Адрес()).Равно(ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost")); + ОжидаемыйПорт = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"); + ОжидаемаяБаза = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres"); + Ожидаем.Что(Строка(Событие.Порт())).Равно(ОжидаемыйПорт); + Ожидаем.Что(Событие.БазаДанных()).Равно(ОжидаемаяБаза); + КонецЕсли; + + Операция = Наблюдатель.Завершенные("Операция", "Сохранить")[0]; + Ожидаем.Что(Операция.Система(), "Описание соединения есть и у событий операций").Равно(Событие.Система()); + +КонецПроцедуры + +// Проверка идет по строке с заведомо уникальным паролем, а не по паролю тестовой базы: +// в CI пароль, пользователь и база называются одинаково, и "не содержит пароль" +// там неотличимо от "не содержит имя базы". +&Тест +Процедура ОписаниеСоединенияНеСодержитПароля() Экспорт + + Пароль = "СекретныйПарольНаблюдателей"; + СтрокаСоединения = "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=" + Пароль; + + Описание = СтрокиСоединения.ОписаниеСервера("postgresql", СтрокаСоединения, 5432); + + Ожидаем.Что(Описание.Количество(), "В описании только система, адрес, порт и база").Равно(4); + Для Каждого Поле Из Описание Цикл + Ожидаем.Что(Строка(Поле.Значение), Поле.Ключ).Не_().Содержит(Пароль); + КонецЦикла; + Ожидаем.Что(Описание.БазаДанных).Равно("postgres"); + +КонецПроцедуры + +&Тест +Процедура ПортИзСтрокиСоединенияПриводитсяКЧислуИлиБеретсяПоУмолчанию() Экспорт + + Ожидаем.Что(СтрокиСоединения.ОписаниеСервера("postgresql", "Host=db;Port=6543", 5432).Порт).Равно(6543); + Ожидаем.Что(СтрокиСоединения.ОписаниеСервера("postgresql", "Host=db;Port=нет", 5432).Порт).Равно(5432); + Ожидаем.Что(СтрокиСоединения.ОписаниеСервера("postgresql", "Host=db", 5432).Порт).Равно(5432); + +КонецПроцедуры + +// Служебные + +Функция НовыйАвтор(Знач Имя) + Автор = Новый Автор; + Автор.Имя = Имя; + Возврат Автор; +КонецФункции + +Процедура ПодключитьФикстуры() + + Фикстуры = Новый Массив; + Фикстуры.Добавить("СущностьБезГенерируемогоИдентификатора"); + Фикстуры.Добавить("Автор"); + Фикстуры.Добавить("НаблюдательЗаписывающий"); + + Для Каждого Фикстура Из Фикстуры Цикл + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", Фикстура + ".os"), + Фикстура + ); + КонецЦикла; + +КонецПроцедуры + +Функция СтрокаСоединенияPostgres() + + Хост = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"); + Порт = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"); + Пользователь = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"); + Пароль = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"); + ИмяБД = ТестовыеУтилиты.ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres"); + + Возврат СтрШаблон( + "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5;", + Хост, + Порт, + Пользователь, + Пароль, + ИмяБД + ); + +КонецФункции diff --git "a/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" new file mode 100644 index 00000000..51e35a69 --- /dev/null +++ "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -0,0 +1,556 @@ +// Наблюдатели источника данных: регистрация и события уровня операции. +// +// Наблюдатель регистрируется на источнике данных, а менеджер создается из источника и отдает +// наблюдателям события всех операций: самого менеджера, его хранилищ и Active Record. +// База - InMemory: события операций от коннектора не зависят. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off + +#Использовать ".." +#Использовать "utils" + +// Источник данных, на котором зарегистрирован наблюдатель. +Перем Источник; +// Менеджер, который тесты создают заново перед каждым тестом. +Перем МенеджерСущностей; +// Наблюдатель, зарегистрированный на источнике до создания менеджера. +Перем Наблюдатель; +// Массив из ИсточникДанных - Источники, созданные тестом: тест может заменить источник новым, +// и закрыть нужно все, а не только последний. +Перем СозданныеИсточники; + +&ПередКаждым +Процедура ПередЗапускомТеста() Экспорт + + ПодключитьФикстуры(); + + ХранилищеВПамяти.Очистить("наблюдатели"); + + СозданныеИсточники = Новый Массив; + + СоздатьМенеджерСНаблюдателем(); + +КонецПроцедуры + +// Создает источник с зарегистрированным наблюдателем и менеджер из него, не трогая данные базы в памяти. +Процедура СоздатьМенеджерСНаблюдателем() + + Источник = Новый ИсточникДанных("наблюдатели", Тип("КоннекторInMemory"), "наблюдатели"); + СозданныеИсточники.Добавить(Источник); + // Подготовка и тест идут в разных потоках: с автозакрытием источник умер бы до теста. + Источник.УстановитьАвтоЗакрытие(Ложь); + Наблюдатель = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Наблюдатель); + + МенеджерСущностей = НовыйМенеджер(Источник); + +КонецПроцедуры + +// Создает и инициализирует менеджер из источника с моделью тестовых сущностей. +// +// Параметры: +// ИсточникМенеджера - ИсточникДанных - Источник, из которого создается менеджер. +// +// Возвращаемое значение: +// МенеджерСущностей - Инициализированный менеджер. +// +Функция НовыйМенеджер(Знач ИсточникМенеджера) + + Менеджер = Новый МенеджерСущностей(ИсточникМенеджера); + Менеджер.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Возврат Менеджер; + +КонецФункции + +&ПослеКаждого +Процедура ПослеЗапускаТеста() Экспорт + МенеджерСущностей.Закрыть(); + ТестовыеУтилиты.ЗакрытьИсточники(СозданныеИсточники); +КонецПроцедуры + +// Регистрация + +&Тест +Процедура ДобавитьНаблюдателяОтвергаетОбъектБезОбъявленияИнтерфейса() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(Новый ПростойОбъект); + + Ожидаем.Что(Источник) + .Метод("ДобавитьНаблюдателя", Параметры) + .ВыбрасываетИсключение("НаблюдательИсточникаДанных"); + +КонецПроцедуры + +&Тест +Процедура ДобавитьНаблюдателяОтвергаетНеполнуюРеализацию() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(Новый НаблюдательНеполный); + + // Текст об интерфейсе дает валидатор extends: недостающий метод он не называет. + Ожидаем.Что(Источник) + .Метод("ДобавитьНаблюдателя", Параметры) + .ВыбрасываетИсключение("Не реализован требуемый интерфейс"); + +КонецПроцедуры + +&Тест +Процедура УдаленныйНаблюдательСобытийНеПолучает() Экспорт + + Источник.УдалитьНаблюдателя(Наблюдатель); + КоличествоДоУдаления = Наблюдатель.Записи.Количество(); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Ожидаем.Что(Наблюдатель.Записи).ИмеетДлину(КоличествоДоУдаления); + +КонецПроцедуры + +&Тест +Процедура МенеджерБеретРазмерПулаИзИсточника() Экспорт + + ИсточникСПулом = Новый ИсточникДанных("пул", Тип("КоннекторInMemory"), "наблюдатели", , 3); + СозданныеИсточники.Добавить(ИсточникСПулом); + НаблюдательПула = Новый НаблюдательЗаписывающий; + ИсточникСПулом.ДобавитьНаблюдателя(НаблюдательПула); + + Менеджер = НовыйМенеджер(ИсточникСПулом); + Менеджер.Закрыть(); + + Захваты = НаблюдательПула.Завершенные("Соединение", "Занять"); + Ожидаем.Что(Захваты, "Инициализация занимала соединение").Не_().ИмеетДлину(0); + Ожидаем.Что(Захваты[0].Предел()).Равно(3); + +КонецПроцедуры + +&Тест +Процедура МенеджерыОдногоИсточникаДелятНаблюдателей() Экспорт + + Второй = НовыйМенеджер(Источник); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + Второй.Сохранить(НовыйПростойОбъект(2)); + Второй.Закрыть(); + + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "Сохранить"), "События обоих менеджеров").ИмеетДлину(2); + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "Инициализировать")).ИмеетДлину(2); + +КонецПроцедуры + +&Тест +Процедура НаблюдательДобавленныйПослеСозданияМенеджераПолучаетСобытия() Экспорт + + Поздний = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Поздний); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Ожидаем.Что(Поздний.Завершенные("Операция", "Сохранить"), "Реестр у источника и менеджера общий").ИмеетДлину(1); + +КонецПроцедуры + +// Операции + +&Тест +Процедура СохранениеПорождаетСобытиеОперации() Экспорт + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Начатые = Наблюдатель.Начатые("Операция", "Сохранить"); + Завершенные = Наблюдатель.Завершенные("Операция", "Сохранить"); + + Ожидаем.Что(Начатые).ИмеетДлину(1); + Ожидаем.Что(Завершенные).ИмеетДлину(1); + Ожидаем.Что(Завершенные[0], "В обеих фазах приходит один и тот же объект события").Равно(Начатые[0]); + + Событие = Завершенные[0]; + Ожидаем.Что(Событие).ИмеетТип("СобытиеИсточникаДанных"); + Ожидаем.Что(Событие.Вид()).Равно("Операция"); + Ожидаем.Что(Событие.Операция()).Равно("Сохранить"); + Ожидаем.Что(Событие.ТипСущности()).Равно(Тип("ПростойОбъект")); + Ожидаем.Что(Событие.ИмяТаблицы()).Равно("ПростыеОбъекты"); + Ожидаем.Что(Событие.ТипКоннектора()).Равно(Тип("КоннекторInMemory")); + Ожидаем.Что(Событие.Глубина()).Равно(0); + Ожидаем.Что(Событие.Длительность()).Не_().Меньше(0); + Ожидаем.Что(Событие.Ошибка()).Равно(Неопределено); + +КонецПроцедуры + +&Тест +Процедура ЧтениеСообщаетКоличествоПрочитанныхСтрок() Экспорт + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + МенеджерСущностей.Сохранить(НовыйПростойОбъект(2)); + + МенеджерСущностей.Получить(Тип("ПростойОбъект")); + МенеджерСущностей.ПолучитьОдно(Тип("ПростойОбъект"), "2"); + + Чтения = Наблюдатель.Завершенные("Операция", "Получить"); + Ожидаем.Что(Чтения).ИмеетДлину(1); + Ожидаем.Что(Чтения[0].КоличествоСтрок()).Равно(2); + + ЧтенияОдного = Наблюдатель.Завершенные("Операция", "ПолучитьОдно"); + Ожидаем.Что(ЧтенияОдного).ИмеетДлину(1); + Ожидаем.Что(ЧтенияОдного[0].КоличествоСтрок()).Равно(1); + +КонецПроцедуры + +&Тест +Процедура УдалениеПорождаетСобытиеОперации() Экспорт + + Объект = НовыйПростойОбъект(1); + МенеджерСущностей.Сохранить(Объект); + МенеджерСущностей.Удалить(Объект); + + Удаления = Наблюдатель.Завершенные("Операция", "Удалить"); + Ожидаем.Что(Удаления).ИмеетДлину(1); + Ожидаем.Что(Удаления[0].ТипСущности()).Равно(Тип("ПростойОбъект")); + +КонецПроцедуры + +&Тест +Процедура ИнициализацияПорождаетСобытиеОперации() Экспорт + + Инициализации = Наблюдатель.Завершенные("Операция", "Инициализировать"); + + Ожидаем.Что(Инициализации).ИмеетДлину(1); + Ожидаем.Что(Инициализации[0].ТипСущности()).Равно(Неопределено); + Ожидаем.Что(Инициализации[0].Ошибка()).Равно(Неопределено); + +КонецПроцедуры + +&Тест +Процедура ПрямойДоступПорождаетСобытиеОперации() Экспорт + + Результат = МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)"); + МенеджерСущностей.ВыполнитьСКоннектором("Коннектор -> Коннектор.Открыт()"); + + Ожидаем.Что(Результат).Равно(Тип("КоннекторInMemory")); + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "ВычислитьСКоннектором")).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "ВыполнитьСКоннектором")).ИмеетДлину(1); + +КонецПроцедуры + +&Тест +Процедура РазыменованиеСсылкиДаетВложенноеСобытие() Экспорт + + Внешняя = Новый СущностьБезГенерируемогоИдентификатора; + Внешняя.ВнутреннийИдентификатор = 7; + МенеджерСущностей.Сохранить(Внешняя); + + Автор = Новый Автор; + Автор.Имя = "Пушкин"; + Автор.ВнешняяСущность = Внешняя; + МенеджерСущностей.Сохранить(Автор); + + // Пул сущностей уже знает внешнюю сущность, поэтому читаем новым менеджером над теми же данными. + МенеджерСущностей.Закрыть(); + СоздатьМенеджерСНаблюдателем(); + + Авторы = МенеджерСущностей.Получить(Тип("Автор")); + Ожидаем.Что(Авторы).ИмеетДлину(1); + + Внешнее = Наблюдатель.Завершенные("Операция", "Получить"); + Вложенные = Наблюдатель.Завершенные("Операция", "ПолучитьОдно"); + + Ожидаем.Что(Внешнее).ИмеетДлину(1); + Ожидаем.Что(Внешнее[0].Глубина()).Равно(0); + Ожидаем.Что(Внешнее[0].ТипСущности()).Равно(Тип("Автор")); + + Ожидаем.Что(Вложенные).ИмеетДлину(1); + Ожидаем.Что(Вложенные[0].Глубина()).Равно(1); + Ожидаем.Что(Вложенные[0].ТипСущности()).Равно(Тип("СущностьБезГенерируемогоИдентификатора")); + +КонецПроцедуры + +&Тест +Процедура ОшибкаОперацииПопадаетВСобытиеИДоходитДоВызывающего() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить("Коннектор -> Коннектор.НесуществующийМетод()"); + + Ожидаем.Что(МенеджерСущностей) + .Метод("ВыполнитьСКоннектором", Параметры) + .ВыбрасываетИсключение("НесуществующийМетод"); + + Ошибочные = Наблюдатель.Завершенные("Операция", "ВыполнитьСКоннектором"); + Ожидаем.Что(Ошибочные).ИмеетДлину(1); + Ожидаем.Что(Ошибочные[0].Ошибка()).ИмеетТип("ИнформацияОбОшибке"); + Ожидаем.Что(Ошибочные[0].Ошибка().Описание).Содержит("НесуществующийМетод"); + +КонецПроцедуры + +&Тест +Процедура ИсключениеНаблюдателяНеЛомаетОперацию() Экспорт + + СломанныйНаблюдатель = Новый НаблюдательЗаписывающий(Истина); + Источник.ДобавитьНаблюдателя(СломанныйНаблюдатель); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Ожидаем.Что(МенеджерСущностей.Получить(Тип("ПростойОбъект"))).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "Сохранить"), "Исправный наблюдатель события получает").ИмеетДлину(1); + ЗавершенияСломанного = СломанныйНаблюдатель.Завершенные("Операция", "Сохранить"); + Ожидаем.Что(ЗавершенияСломанного, "Сломанному наблюдателю завершение все равно приходит").ИмеетДлину(1); + +КонецПроцедуры + +&Тест +Процедура СобытияХранилищаИАктивнойЗаписиПриходятНаблюдателямИсточникаДанных() Экспорт + + Хранилище = МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("ПростойОбъект")); + Хранилище.Сохранить(НовыйПростойОбъект(1)); + + Элемент = МенеджерСущностей.СоздатьЭлемент(Тип("ПростойОбъект")); + Элемент.Идентификатор = 2; + Элемент.Сохранить(); + + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "Сохранить")).ИмеетДлину(2); + +КонецПроцедуры + +&Тест +Процедура СостояниеНаблюдателяЖиветВСобытииМеждуФазами() Экспорт + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Событие = Наблюдатель.Завершенные("Операция", "Сохранить")[0]; + Ожидаем.Что(Событие.Состояние()).Равно(Неопределено); + + Событие.УстановитьСостояние("метка"); + Ожидаем.Что(Событие.Состояние()).Равно("метка"); + +КонецПроцедуры + +&Тест +Процедура СостояниеУКаждогоНаблюдателяСвое() Экспорт + + // Дано - два наблюдателя кладут в событие разные метки + Первый = Новый НаблюдательСМеткой("первый"); + Второй = Новый НаблюдательСМеткой("второй"); + Источник.ДобавитьНаблюдателя(Первый); + Источник.ДобавитьНаблюдателя(Второй); + + // Когда + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + // Тогда - каждый в ПослеСобытия читает свою метку, а не последнюю записанную + Ожидаем.Что(Первый.Увиденные).Не_().ИмеетДлину(0); + Ожидаем.Что(Второй.Увиденные).ИмеетДлину(Первый.Увиденные.Количество()); + Для Каждого Состояние Из Первый.Увиденные Цикл + Ожидаем.Что(Состояние).Равно("первый"); + КонецЦикла; + Для Каждого Состояние Из Второй.Увиденные Цикл + Ожидаем.Что(Состояние).Равно("второй"); + КонецЦикла; + + // Записывающий наблюдатель ничего не клал - и ничего не видит + Событие = Наблюдатель.Завершенные("Операция", "Сохранить")[0]; + Ожидаем.Что(Событие.Состояние()).Равно(Неопределено); + +КонецПроцедуры + +&Тест +Процедура СнятыйВоВремяСобытияНаблюдательПолучаетЕгоЗавершение() Экспорт + + // Дано - наблюдатель, который в первом же ПередСобытием снимает себя с регистрации + Отписывающийся = Новый НаблюдательПереподписывающийся(Источник); + Источник.ДобавитьНаблюдателя(Отписывающийся); + + // Когда + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + // Тогда - состав получателей фиксируется при начале события + Ожидаем.Что(Отписывающийся.Начатые, "Снял себя в первом событии, вложенных не видел").ИмеетДлину(1); + Ожидаем.Что(Отписывающийся.Завершенные, "Начатое событие доходит до завершения").ИмеетДлину(1); + Ожидаем.Что(Отписывающийся.Завершенные[0]).Равно(Отписывающийся.Начатые[0]); + +КонецПроцедуры + +&Тест +Процедура ДобавленныйВоВремяСобытияНаблюдательЕгоЗавершенияНеПолучает() Экспорт + + // Дано - наблюдатель, который в первом же ПередСобытием регистрирует другого + Новичок = Новый НаблюдательЗаписывающий; + Подписывающий = Новый НаблюдательПереподписывающийся(Источник, Новичок); + Источник.ДобавитьНаблюдателя(Подписывающий); + + // Когда + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + // Тогда + Ожидаем.Что(Новичок.Завершенные("Операция", "Сохранить"), "Событие началось до регистрации").ИмеетДлину(0); + Ожидаем.Что(Новичок.Завершенные("Запрос", "INSERT"), "Вложенные события начались после").ИмеетДлину(1); + +КонецПроцедуры + +// Коннектор + +&Тест +Процедура ОшибкаПодключенияНаблюдателейЗакрываетОткрытыйКоннектор() Экспорт + + МенеджерСущностей.Закрыть(); + + // Наблюдатели на источнике не нужны: коннектор подключается к реестру всегда. + Источник = Новый ИсточникДанных("наблюдатели", Тип("КоннекторСЛоманымОписанием"), "наблюдатели"); + СозданныеИсточники.Добавить(Источник); + МенеджерСущностей = Новый МенеджерСущностей(Источник); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + + Ожидаем.Что(МенеджерСущностей) + .Метод("Инициализировать", Новый Массив) + .ВыбрасываетИсключение("Описание соединения недоступно"); + + Закрыт = ТекущийПоток().Данные.Получить(Новый КоннекторСЛоманымОписанием().КлючЗакрыт()); + Ожидаем.Что(Закрыт, "Открытый коннектор закрыт после ошибки подключения наблюдателей").Равно(Истина); + +КонецПроцедуры + +&Тест +Процедура НеполноНаблюдаемыйКоннекторОтвергаетсяКаждыйРаз() Экспорт + + МенеджерСущностей.Закрыть(); + + Источник = Новый ИсточникДанных("неполный", Тип("КоннекторНеполноНаблюдаемый"), "неполный"); + СозданныеИсточники.Добавить(Источник); + МенеджерСущностей = Новый МенеджерСущностей(Источник); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + + Ожидаем.Что(МенеджерСущностей) + .Метод("Инициализировать", Новый Массив) + .ВыбрасываетИсключение("Не реализован требуемый интерфейс"); + + // Проверку нельзя считать пройденной раньше, чем она прошла: иначе второе открытие соединения + // пропустит валидатор и вместо разговора про интерфейс отдаст ошибку ненайденного метода. + Ожидаем.Что(МенеджерСущностей, "Второе открытие отвергается тем же валидатором") + .Метод("Инициализировать", Новый Массив) + .ВыбрасываетИсключение("Не реализован требуемый интерфейс"); + +КонецПроцедуры + +&Тест +Процедура ОперацииКоннектораВПамятиПорождаютСобытияЗапросаБезТекста() Экспорт + + Объект = НовыйПростойОбъект(1); + МенеджерСущностей.Сохранить(Объект); + МенеджерСущностей.Получить(Тип("ПростойОбъект")); + МенеджерСущностей.Удалить(Объект); + + Создания = Наблюдатель.Завершенные("Запрос", "CREATE TABLE"); + Ожидаем.Что(Создания, "По событию на каждую таблицу модели").ИмеетДлину(3); + + Вставки = Наблюдатель.Завершенные("Запрос", "INSERT"); + Ожидаем.Что(Вставки).ИмеетДлину(1); + Ожидаем.Что(Вставки[0].ИмяТаблицы()).Равно("ПростыеОбъекты"); + Ожидаем.Что(Вставки[0].ТекстЗапроса(), "Коннектор без SQL текста запроса не формирует").Равно(Неопределено); + + Выборки = Наблюдатель.Завершенные("Запрос", "SELECT"); + Ожидаем.Что(Выборки).ИмеетДлину(1); + Ожидаем.Что(Выборки[0].КоличествоСтрок()).Равно(1); + + Ожидаем.Что(Наблюдатель.Завершенные("Запрос", "DELETE")).ИмеетДлину(1); + +КонецПроцедуры + +&Тест +Процедура СобытияНесутОписаниеСоединения() Экспорт + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Событие = Наблюдатель.Завершенные("Операция", "Сохранить")[0]; + Ожидаем.Что(Событие.Система()).Равно("inmemory"); + Ожидаем.Что(Событие.БазаДанных()).Равно("наблюдатели"); + Ожидаем.Что(Событие.Адрес()).Равно(""); + Ожидаем.Что(Событие.Порт()).Равно(0); + + Запрос = Наблюдатель.Завершенные("Запрос", "INSERT")[0]; + Ожидаем.Что(Запрос.Система()).Равно("inmemory"); + Ожидаем.Что(Запрос.БазаДанных()).Равно("наблюдатели"); + +КонецПроцедуры + +&Тест +Процедура СобытияДоОткрытияПервогоСоединенияПолучаютОписаниеКЗавершению() Экспорт + + // Инициализация и ее захват соединения начинаются, когда ни один коннектор еще не открыт + // и описания соединения нет; к завершению события оно уже известно. + Инициализация = Наблюдатель.Завершенные("Операция", "Инициализировать")[0]; + ПервыйЗахват = Наблюдатель.Завершенные("Соединение", "Занять")[0]; + + Ожидаем.Что(Инициализация.БазаДанных()).Равно("наблюдатели"); + Ожидаем.Что(ПервыйЗахват.Система()).Равно("inmemory"); + Ожидаем.Что(ПервыйЗахват.БазаДанных()).Равно("наблюдатели"); + +КонецПроцедуры + +&Тест +Процедура КоннекторБезИнтерфейсаНаблюдаемыйКоннекторДаетТолькоСобытияОпераций() Экспорт + + МенеджерСущностей.Закрыть(); + + Источник = Новый ИсточникДанных("наблюдатели", Тип("КоннекторБезНаблюдения"), "наблюдатели"); + СозданныеИсточники.Добавить(Источник); + Наблюдатель = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Наблюдатель); + МенеджерСущностей = Новый МенеджерСущностей(Источник); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + МенеджерСущностей.Инициализировать(); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + Ожидаем.Что(МенеджерСущностей.Получить(Тип("ПростойОбъект"))).ИмеетДлину(1); + + Ожидаем.Что(Наблюдатель.Завершенные("Операция")).ИмеетДлину(3); + Ожидаем.Что(Наблюдатель.Завершенные("Запрос")).ИмеетДлину(0); + + Событие = Наблюдатель.Завершенные("Операция", "Сохранить")[0]; + Ожидаем.Что(Событие.Система(), "Без описания от коннектора система выводится из имени типа") + .Равно(НРег("КоннекторБезНаблюдения")); + Ожидаем.Что(Событие.БазаДанных()).Равно(""); + +КонецПроцедуры + +// Служебные + +// Колонка идентификатора ПростойОбъект строковая: без &Колонка тип колонки - Строка. +// +// Параметры: +// Идентификатор - Строка, Число - Идентификатор объекта, приводится к строке. +// +// Возвращаемое значение: +// ПростойОбъект - Объект с заполненными идентификатором и полем. +// +Функция НовыйПростойОбъект(Знач Идентификатор) + Объект = Новый ПростойОбъект; + Объект.Идентификатор = Строка(Идентификатор); + Объект.Поле = "поле " + Идентификатор; + Возврат Объект; +КонецФункции + +Процедура ПодключитьФикстуры() + + Фикстуры = Новый Массив; + Фикстуры.Добавить("ПростойОбъект"); + Фикстуры.Добавить("СущностьБезГенерируемогоИдентификатора"); + Фикстуры.Добавить("Автор"); + Фикстуры.Добавить("НаблюдательЗаписывающий"); + Фикстуры.Добавить("НаблюдательНеполный"); + Фикстуры.Добавить("НаблюдательСМеткой"); + Фикстуры.Добавить("НаблюдательПереподписывающийся"); + Фикстуры.Добавить("КоннекторБезНаблюдения"); + Фикстуры.Добавить("КоннекторСЛоманымОписанием"); + Фикстуры.Добавить("КоннекторНеполноНаблюдаемый"); + + Для Каждого Фикстура Из Фикстуры Цикл + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", Фикстура + ".os"), + Фикстура + ); + КонецЦикла; + +КонецПроцедуры diff --git "a/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os" "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os" new file mode 100644 index 00000000..2ae84909 --- /dev/null +++ "b/tests/\320\235\320\260\320\261\320\273\321\216\320\264\320\260\321\202\320\265\320\273\320\270\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os" @@ -0,0 +1,688 @@ +// Наблюдатели источника данных: события соединений пула и транзакций. +// +// База - InMemory за коннектором-обёрткой, изображающим транзакции: события соединений и +// транзакций рождаются в пуле и в обёртке соединения, от СУБД они не зависят. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off + +#Использовать ".." +#Использовать "utils" + +// Источник данных, из которого тесты создают менеджер. +Перем Источник; +// Менеджер, который тесты создают заново перед каждым тестом. +Перем МенеджерСущностей; +// Наблюдатель, зарегистрированный на менеджере до инициализации. +Перем Наблюдатель; +// ИнформацияОбОшибке - Ошибка фиксации транзакции в фоновом задании; Неопределено, если ее не было. +Перем ОшибкаФиксацииВФоне; +// Массив из ИсточникДанных - Источники, созданные тестом: тест может создать менеджер заново, +// и закрыть нужно все источники, а не только последний. +Перем СозданныеИсточники; + +&ПередКаждым +Процедура ПередЗапускомТеста() Экспорт + + ПодключитьФикстуры(); + ХранилищеВПамяти.Очистить("соединения"); + + ОшибкаФиксацииВФоне = Неопределено; + СозданныеИсточники = Новый Массив; + + СоздатьМенеджер(10); + +КонецПроцедуры + +&ПослеКаждого +Процедура ПослеЗапускаТеста() Экспорт + МенеджерСущностей.Закрыть(); + ТестовыеУтилиты.ЗакрытьИсточники(СозданныеИсточники); +КонецПроцедуры + +// Соединения + +&Тест +Процедура ЗахватИОсвобождениеСоединенияПорождаютСобытия() Экспорт + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Захваты = Наблюдатель.Завершенные("Соединение", "Занять"); + Освобождения = Наблюдатель.Завершенные("Соединение", "Освободить"); + + Ожидаем.Что(Захваты, "Инициализация и сохранение занимают соединение по разу").ИмеетДлину(2); + Ожидаем.Что(Освобождения).ИмеетДлину(2); + + Первый = Захваты[0]; + Ожидаем.Что(Первый.Открыто(), "Первый захват открывает новое соединение").Равно(Истина); + Ожидаем.Что(Первый.ВремяОткрытия()).Не_().Меньше(0); + Ожидаем.Что(Первый.Ожидание()).Равно(0); + Ожидаем.Что(Первый.Ошибка()).Равно(Неопределено); + + Второй = Захваты[1]; + Ожидаем.Что(Второй.Открыто(), "Второй захват берет свободное соединение").Равно(Ложь); + Ожидаем.Что(Второй.ВремяОткрытия()).Равно(0); + Ожидаем.Что(Второй.Занято(), "Снимок пула после захвата").Равно(1); + Ожидаем.Что(Второй.Свободно()).Равно(0); + + Последнее = Освобождения[1]; + Ожидаем.Что(Последнее.Занято(), "Снимок пула после освобождения").Равно(0); + Ожидаем.Что(Последнее.Свободно()).Равно(1); + Ожидаем.Что(Последнее.Ожидает()).Равно(0); + Ожидаем.Что(Последнее.Предел()).Равно(10); + +КонецПроцедуры + +&Тест +Процедура ПовторныйЗахватТогоЖеПотокаСобытийНеПорождает() Экспорт + + Внешняя = Новый СущностьБезГенерируемогоИдентификатора; + Внешняя.ВнутреннийИдентификатор = 7; + МенеджерСущностей.Сохранить(Внешняя); + + Автор = Новый Автор; + Автор.Имя = "Пушкин"; + Автор.ВнешняяСущность = Внешняя; + МенеджерСущностей.Сохранить(Автор); + + МенеджерСущностей.Закрыть(); + СоздатьМенеджер(10); + + ЗахватовДо = Наблюдатель.Завершенные("Соединение", "Занять").Количество(); + МенеджерСущностей.Получить(Тип("Автор")); + + Ожидаем.Что(Наблюдатель.Завершенные("Операция", "ПолучитьОдно"), "Ссылка разыменована вложенной операцией") + .ИмеетДлину(1); + ЗахватовВложеннойОперацией = Наблюдатель.Завершенные("Соединение", "Занять").Количество() - ЗахватовДо; + Ожидаем.Что(ЗахватовВложеннойОперацией, "Вложенная операция идет по тому же соединению").Равно(1); + +КонецПроцедуры + +&Тест +Процедура ОжиданиеСоединенияПопадаетВСобытие() Экспорт + + МенеджерСущностей.Закрыть(); + СоздатьМенеджер(1); + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ДержатьСоединениеВФоне", Новый Массив, Истина); + // Транзакция задания начинается на занятом им соединении: ее начало и есть сигнал, что + // единственное соединение пула занято и основному потоку придется ждать. + Ожидаем.Что(ДождатьсяНачатыхТранзакций(1), "Задание заняло соединение").ИмеетДлину(1); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + ДождатьсяЗадания(Задание); + + Захваты = Наблюдатель.Завершенные("Соединение", "Занять"); + Последний = Захваты[Захваты.ВГраница()]; + + Ожидаем.Что(Последний.Ожидание(), "Основной поток ждал соединение, занятое заданием").Не_().Меньше(100); + Ожидаем.Что(Последний.Открыто()).Равно(Ложь); + + // Снимок берется после того, как ожидающий получил соединение: в очереди больше никого, и + // собственная отработавшая заявка потока в ожидающих числиться не должна. + Ожидаем.Что(Последний.Ожидает(), "Дождавшийся не считается ожидающим").Равно(0); + Ожидаем.Что(Источник.ПулСоединений().КоличествоОжидающих(), "Очередь пуста").Равно(0); + +КонецПроцедуры + +&Тест +Процедура СобытиеЗахватаОхватываетОжиданиеБлокировкиПула() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + Настройки.ПаузаЗакрытия = 600; + Настройки.СигналЗакрытия = Новый СигналСобытия; + СоздатьМенеджер(1, Настройки); + + // Задание портит единственное соединение пула неудачной фиксацией. Возврат испорченного + // соединения закрывает его, а закрытие идет под блокировкой записи пула - основному потоку + // придется стоять на ней, еще не начав захват. + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ИспортитьСоединениеВФоне", Новый Массив, Истина); + + // Ждем не события транзакции, а входа в закрытие: только он означает, что блокировка пула + // уже занята заданием. Пойди основной поток раньше - он занял бы соединение сам или встал + // в очередь, и мерить было бы нечего. + Ожидаем.Что(ДождатьсяСигнала(Настройки.СигналЗакрытия), "Задание дошло до закрытия соединения") + .ЭтоИстина(); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + ДождатьсяЗадания(Задание); + + Ожидаем.Что(ОшибкаФиксацииВФоне, "Задание должно было увидеть ошибку фиксации") + .ИмеетТип("ИнформацияОбОшибке"); + + Захваты = Наблюдатель.Завершенные("Соединение", "Занять"); + Последний = Захваты[Захваты.ВГраница()]; + + Ожидаем.Что(Последний.Ожидание(), "В очередь поток не вставал: он стоял на блокировке").Равно(0); + Ожидаем.Что(Последний.Длительность(), "Событие захвата начинается до блокировки пула").Не_().Меньше(300); + +КонецПроцедуры + +&Тест +Процедура СобытиеОсвобожденияОхватываетВозвратСоединения() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + Настройки.ПаузаЗакрытия = 600; + СоздатьМенеджер(10, Настройки); + + МенеджерСущностей.НачатьТранзакцию(); + + // Неудачная фиксация делает соединение непригодным. Возврат такого соединения закрывает его, + // и закрытие идет под блокировкой записи пула, то есть внутри освобождения. + Ожидаем.Что(МенеджерСущностей) + .Метод("ЗафиксироватьТранзакцию", Новый Массив) + .ВыбрасываетИсключение("Завершение транзакции не удалось"); + + Освобождения = Наблюдатель.Завершенные("Соединение", "Освободить"); + Последнее = Освобождения[Освобождения.ВГраница()]; + + Ожидаем.Что(Последнее.Длительность(), "Событие освобождения охватывает закрытие соединения") + .Не_().Меньше(300); + +КонецПроцедуры + +&Тест +Процедура ОшибкаОсвобожденияНеОставляетСобытиеОперацииНезавершенным() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + // Падает только первое закрытие - то, которым пул избавляется от испорченного соединения. + Настройки.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + СоздатьМенеджер(10, Настройки); + + Контекст = Новый Структура("Менеджер", МенеджерСущностей); + Параметры = Новый Массив; + Параметры.Добавить("Коннектор -> Менеджер.ЗафиксироватьТранзакцию()"); + Параметры.Добавить(Контекст); + + // Неудачная фиксация делает соединение непригодным, возврат такого соединения закрывает его, + // а закрытие падает: освобождение внутри операции завершается ошибкой. Наружу должна идти + // причина - ошибка операции, а не ошибка освобождения. + Ожидаем.Что(МенеджерСущностей) + .Метод("ВыполнитьСКоннектором", Параметры) + .ВыбрасываетИсключение("Завершение транзакции не удалось"); + + Операции = Наблюдатель.Завершенные("Операция", "ВыполнитьСКоннектором"); + Ожидаем.Что(Операции, "Событие операции завершено, несмотря на ошибку освобождения").ИмеетДлину(1); + Ожидаем.Что(Операции[0].Ошибка()).ИмеетТип("ИнформацияОбОшибке"); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + + Сохранения = Наблюдатель.Завершенные("Операция", "Сохранить"); + Ожидаем.Что(Сохранения[Сохранения.ВГраница()].Глубина(), "Вложенность операций потока не съехала").Равно(0); + +КонецПроцедуры + +&Тест +Процедура ВозвратСоединенияУмершегоПотокаДаетСобытиеОсвобождения() Экспорт + + ОсвобожденийДо = Наблюдатель.Завершенные("Соединение", "Освободить").Количество(); + + // Задание занимает соединение транзакцией и обрывается, не освободив его: соединение + // возвращает обработчик завершения потока, и это такой же возврат в пул. + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ОборватьТранзакциюВФоне", Новый Массив, Истина); + ДождатьсяЗадания(Задание); + + Освобождения = ДождатьсяОсвобождений(ОсвобожденийДо + 1); + + Ожидаем.Что(Освобождения, "Возврат соединения умершего потока - тоже освобождение") + .ИмеетДлину(ОсвобожденийДо + 1); + + Последнее = Освобождения[Освобождения.ВГраница()]; + Ожидаем.Что(Последнее.Занято(), "Снимок пула после возврата").Равно(0); + Ожидаем.Что(Последнее.Свободно()).Равно(1); + +КонецПроцедуры + +&Тест +Процедура ОшибкаЗакрытияНеОставляетЗахватБезОсвобождения() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + // Падает только первое закрытие - то, которым пул избавляется от испорченного соединения. + Настройки.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + СоздатьМенеджер(10, Настройки); + + Контекст = Новый Структура("Менеджер", МенеджерСущностей); + Параметры = Новый Массив; + Параметры.Добавить("Коннектор -> Менеджер.ЗафиксироватьТранзакцию()"); + Параметры.Добавить(Контекст); + + // Соединение отобрано у потока и место в пуле возвращено еще до закрытия, поэтому возврат + // состоялся, как бы ни кончилось закрытие. Событие захвата без пары оставило бы наблюдателю + // незакрытый спан и незанятое, но так и не освобожденное соединение в счетчиках. + Ожидаем.Что(МенеджерСущностей) + .Метод("ВыполнитьСКоннектором", Параметры) + .ВыбрасываетИсключение("Завершение транзакции не удалось"); + + Захваты = Наблюдатель.Завершенные("Соединение", "Занять"); + Освобождения = Наблюдатель.Завершенные("Соединение", "Освободить"); + + Ожидаем.Что(Освобождения, "У каждого захвата есть освобождение, даже если закрытие упало") + .ИмеетДлину(Захваты.Количество()); + + Последнее = Освобождения[Освобождения.ВГраница()]; + Ожидаем.Что(Последнее.Ошибка(), "Ошибка закрытия - это ошибка освобождения") + .ИмеетТип("ИнформацияОбОшибке"); + +КонецПроцедуры + +&Тест +Процедура ОшибкаЗакрытияНеОтменяетОсвобождениеУмершегоПотока() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + // Откат брошенной транзакции падает, соединение становится непригодным, и пул его закрывает - + // а закрытие роняет план. + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + Настройки.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + СоздатьМенеджер(10, Настройки); + + ОсвобожденийДо = Наблюдатель.Завершенные("Соединение", "Освободить").Количество(); + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ОборватьТранзакциюВФоне", Новый Массив, Истина); + ДождатьсяЗадания(Задание); + + Освобождения = ДождатьсяОсвобождений(ОсвобожденийДо + 1); + + Ожидаем.Что(Освобождения, "Возврат за умершим потоком - освобождение, даже если закрытие упало") + .ИмеетДлину(ОсвобожденийДо + 1); + + Последнее = Освобождения[Освобождения.ВГраница()]; + Ожидаем.Что(Последнее.Ошибка(), "Ошибка закрытия - это ошибка освобождения") + .ИмеетТип("ИнформацияОбОшибке"); + +КонецПроцедуры + +&Тест +Процедура НаблюдателиНеЗовутсяИзПодБлокировкиПула() Экспорт + + МенеджерСущностей.Закрыть(); + СоздатьМенеджер(2); + + // Наблюдатель засыпает на завершении события транзакции. Такое событие завершается при + // возврате брошенного соединения, то есть в тот момент, когда пул держит свою блокировку. + СигналПаузы = Новый СигналСобытия; + Тормоз = Новый НаблюдательТормозящий(ВидыСобытийИсточникаДанных.Транзакция(), 600, СигналПаузы); + Источник.ДобавитьНаблюдателя(Тормоз); + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ОборватьТранзакциюВФоне", Новый Массив, Истина); + + // Ждать завершения задания нельзя: обработчик завершения потока успел бы отработать целиком, + // вместе со сном наблюдателя, и мерить было бы нечего. Ждем сам сон. + Ожидаем.Что(ДождатьсяСигнала(СигналПаузы), "Наблюдатель дождался брошенной транзакции").ЭтоИстина(); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + ДождатьсяЗадания(Задание); + + Захваты = Наблюдатель.Завершенные("Соединение", "Занять"); + Последний = Захваты[Захваты.ВГраница()]; + + Ожидаем.Что(Последний.Длительность(), "Спящий наблюдатель не держит блокировку пула").Меньше(300); + +КонецПроцедуры + +// Транзакции + +&Тест +Процедура ФиксацияДаетСобытиеТранзакцииСИсходомCommit() Экспорт + + МенеджерСущностей.НачатьТранзакцию(); + + Ожидаем.Что(Наблюдатель.Начатые("Транзакция")).ИмеетДлину(1); + Ожидаем.Что(Наблюдатель.Завершенные("Транзакция")).ИмеетДлину(0); + + МенеджерСущностей.Сохранить(НовыйПростойОбъект(1)); + МенеджерСущностей.ЗафиксироватьТранзакцию(); + + Транзакции = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Транзакции).ИмеетДлину(1); + Ожидаем.Что(Транзакции[0].Исход()).Равно("commit"); + Ожидаем.Что(Транзакции[0].Ошибка()).Равно(Неопределено); + Ожидаем.Что(Транзакции[0].Длительность()).Не_().Меньше(0); + Ожидаем.Что(Транзакции[0], "В обеих фазах один объект").Равно(Наблюдатель.Начатые("Транзакция")[0]); + +КонецПроцедуры + +&Тест +Процедура ОтменаДаетСобытиеТранзакцииСИсходомRollback() Экспорт + + МенеджерСущностей.НачатьТранзакцию(); + МенеджерСущностей.ОтменитьТранзакцию(); + + Транзакции = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Транзакции).ИмеетДлину(1); + Ожидаем.Что(Транзакции[0].Исход()).Равно("rollback"); + +КонецПроцедуры + +&Тест +Процедура ОшибкаЗавершенияДаетИсходFailed() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриЗавершенииТранзакции = Истина; + СоздатьМенеджер(10, Настройки); + + МенеджерСущностей.НачатьТранзакцию(); + + Ожидаем.Что(МенеджерСущностей) + .Метод("ЗафиксироватьТранзакцию", Новый Массив) + .ВыбрасываетИсключение("Завершение транзакции не удалось"); + + Транзакции = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Транзакции).ИмеетДлину(1); + Ожидаем.Что(Транзакции[0].Исход()).Равно("failed"); + Ожидаем.Что(Транзакции[0].Ошибка()).ИмеетТип("ИнформацияОбОшибке"); + +КонецПроцедуры + +&Тест +Процедура ОшибкаНачалаТранзакцииДаетИсходFailed() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ПадатьПриНачалеТранзакции = Истина; + СоздатьМенеджер(10, Настройки); + + Ожидаем.Что(МенеджерСущностей) + .Метод("НачатьТранзакцию", Новый Массив) + .ВыбрасываетИсключение("Начало транзакции не удалось"); + + Транзакции = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Транзакции, "Событие начато до BEGIN и завершено его ошибкой").ИмеетДлину(1); + Ожидаем.Что(Транзакции[0].Исход()).Равно("failed"); + Ожидаем.Что(Транзакции[0].Ошибка()).ИмеетТип("ИнформацияОбОшибке"); + +КонецПроцедуры + +&Тест +Процедура БрошеннаяТранзакцияЗавершаетсяИсходомAbandoned() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ОборватьТранзакциюВФоне", Новый Массив, Истина); + ДождатьсяЗадания(Задание); + + Ожидаем.Что(Задание.ИнформацияОбОшибке <> Неопределено, "Задание должно было оборваться").ЭтоИстина(); + + // Соединение возвращается обработчиком завершения потока; ему дается время сработать. + Транзакции = ДождатьсяЗавершенныхТранзакций(1); + + Ожидаем.Что(Транзакции).ИмеетДлину(1); + Ожидаем.Что(Транзакции[0].Исход()).Равно("abandoned"); + +КонецПроцедуры + +&Тест +Процедура ОтвергнутоеВложенноеНачалоНеОбрываетСобытиеВнешнейТранзакции() Экспорт + + МенеджерСущностей.Закрыть(); + Настройки = Новый НастройкиКоннектораТранзакций; + Настройки.ОтвергатьВложеннуюТранзакцию = Истина; + СоздатьМенеджер(10, Настройки); + + МенеджерСущностей.НачатьТранзакцию(); + + Ожидаем.Что(МенеджерСущностей) + .Метод("НачатьТранзакцию", Новый Массив) + .ВыбрасываетИсключение("Вложенная транзакция не поддерживается"); + + Завершенные = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Завершенные, "Отвергнутое начало завершает только свое событие").ИмеетДлину(1); + Ожидаем.Что(Завершенные[0].Исход()).Равно("failed"); + + МенеджерСущностей.ЗафиксироватьТранзакцию(); + + Начатые = Наблюдатель.Начатые("Транзакция"); + Ожидаем.Что(Начатые, "Каждое начало транзакции - свое событие").ИмеетДлину(2); + + Завершенные = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Завершенные, "Событие внешней транзакции дожило до фиксации").ИмеетДлину(2); + Ожидаем.Что(Завершенные[1].Исход()).Равно("commit"); + Ожидаем.Что(Завершенные[1], "Зафиксировано событие внешней транзакции").Равно(Начатые[0]); + +КонецПроцедуры + +&Тест +Процедура ЗавершениеЗакрываетВерхнееСобытиеСтопкиТранзакций() Экспорт + + МенеджерСущностей.НачатьТранзакцию(); + МенеджерСущностей.НачатьТранзакцию(); + + Начатые = Наблюдатель.Начатые("Транзакция"); + Ожидаем.Что(Начатые, "Вложенное начало дает свое событие").ИмеетДлину(2); + Ожидаем.Что(Наблюдатель.Завершенные("Транзакция")).ИмеетДлину(0); + + МенеджерСущностей.ЗафиксироватьТранзакцию(); + + Завершенные = Наблюдатель.Завершенные("Транзакция"); + Ожидаем.Что(Завершенные).ИмеетДлину(2); + Ожидаем.Что(Завершенные[0].Исход(), "Фиксация закрывает верхнее событие стопки").Равно("commit"); + Ожидаем.Что(Завершенные[0]).Равно(Начатые[1]); + Ожидаем.Что(Завершенные[1].Исход(), "Остаток стопки брошен возвратом соединения").Равно("abandoned"); + Ожидаем.Что(Завершенные[1]).Равно(Начатые[0]); + +КонецПроцедуры + +&Тест +Процедура БрошеннаяСтопкаТранзакцийЗавершаетсяЦеликом() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ОборватьВложеннуюТранзакциюВФоне", Новый Массив, Истина); + ДождатьсяЗадания(Задание); + + Ожидаем.Что(Задание.ИнформацияОбОшибке <> Неопределено, "Задание должно было оборваться").ЭтоИстина(); + + // Соединение возвращается обработчиком завершения потока; ему дается время сработать. + Транзакции = ДождатьсяЗавершенныхТранзакций(2); + + Ожидаем.Что(Транзакции, "Брошены оба начала").ИмеетДлину(2); + Ожидаем.Что(Транзакции[0].Исход()).Равно("abandoned"); + Ожидаем.Что(Транзакции[1].Исход()).Равно("abandoned"); + +КонецПроцедуры + +// Фоновые процедуры + +// Держит соединение транзакцией, пока основной поток пытается его занять. +// +Процедура ДержатьСоединениеВФоне() Экспорт + МенеджерСущностей.НачатьТранзакцию(); + Приостановить(400); + МенеджерСущностей.ЗафиксироватьТранзакцию(); +КонецПроцедуры + +// Начинает транзакцию и обрывается, не завершив ее. +// +Процедура ОборватьТранзакциюВФоне() Экспорт + МенеджерСущностей.НачатьТранзакцию(); + ВызватьИсключение "Обрыв фонового задания"; +КонецПроцедуры + +// Портит соединение неудачной фиксацией и возвращает его в пул. +// +Процедура ИспортитьСоединениеВФоне() Экспорт + + МенеджерСущностей.НачатьТранзакцию(); + + Попытка + МенеджерСущностей.ЗафиксироватьТранзакцию(); + Исключение + // Ошибка фиксации и нужна: после нее соединение непригодно, и пул его закроет. + ОшибкаФиксацииВФоне = ИнформацияОбОшибке(); + КонецПопытки; + +КонецПроцедуры + +// Начинает вложенную транзакцию и обрывается, не завершив ни одной. +// +Процедура ОборватьВложеннуюТранзакциюВФоне() Экспорт + МенеджерСущностей.НачатьТранзакцию(); + МенеджерСущностей.НачатьТранзакцию(); + ВызватьИсключение "Обрыв фонового задания"; +КонецПроцедуры + +// Служебные + +// Создает источник с тестовым коннектором транзакций, менеджер на нем и наблюдателя. +// +// Параметры: +// РазмерПула - Число - Предел соединений источника. +// Настройки - НастройкиКоннектораТранзакций, Неопределено - Поведение коннектора. +// Неопределено - безотказная база. +// +Процедура СоздатьМенеджер(Знач РазмерПула, Знач Настройки = Неопределено) + + Если Настройки = Неопределено Тогда + Настройки = Новый НастройкиКоннектораТранзакций; + КонецЕсли; + + Источник = Новый ИсточникДанных( + "соединения", + Тип("КоннекторТранзакцийДляТестов"), + "соединения", + Настройки.ПараметрыКоннектора(), + РазмерПула + ); + СозданныеИсточники.Добавить(Источник); + // Подготовка и тест идут в разных потоках: с автозакрытием источник умер бы до теста. + Источник.УстановитьАвтоЗакрытие(Ложь); + Наблюдатель = Новый НаблюдательЗаписывающий; + Источник.ДобавитьНаблюдателя(Наблюдатель); + + МенеджерСущностей = Новый МенеджерСущностей(Источник); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + МенеджерСущностей.ДобавитьКлассВМодель(Тип("Автор")); + + МенеджерСущностей.Инициализировать(); + +КонецПроцедуры + +// Ждет отметку сигнала, не дольше 5 секунд. +// +// Параметры: +// Сигнал - СигналСобытия - Сигнал, который ставит проверяемый код. +// +// Возвращаемое значение: +// Булево - Истина, если отметка появилась; Ложь, если не дождались. +// +Функция ДождатьсяСигнала(Сигнал) + + КрайнийСрок = ТекущаяУниверсальнаяДатаВМиллисекундах() + 5000; + + Пока НЕ Сигнал.Отмечен() Цикл + Если ТекущаяУниверсальнаяДатаВМиллисекундах() > КрайнийСрок Тогда + Возврат Ложь; + КонецЕсли; + Приостановить(5); + КонецЦикла; + + Возврат Истина; + +КонецФункции + +Функция ДождатьсяЗавершенныхТранзакций(Знач Количество) + Возврат ДождатьсяТранзакций("Завершенные", Количество); +КонецФункции + +// Ждет, пока наблюдатель не увидит нужное число завершенных освобождений соединения, +// не дольше 5 секунд. +// +// Параметры: +// Количество - Число - Сколько событий ждать. +// +// Возвращаемое значение: +// Массив из СобытиеИсточникаДанных - События освобождения, сколько бы их ни набралось к сроку. +// +Функция ДождатьсяОсвобождений(Знач Количество) + + КрайнийСрок = ТекущаяУниверсальнаяДатаВМиллисекундах() + 5000; + + Пока Истина Цикл + Освобождения = Наблюдатель.Завершенные("Соединение", "Освободить"); + Если Освобождения.Количество() >= Количество ИЛИ ТекущаяУниверсальнаяДатаВМиллисекундах() > КрайнийСрок Тогда + Возврат Освобождения; + КонецЕсли; + Приостановить(20); + КонецЦикла; + +КонецФункции + +Функция ДождатьсяНачатыхТранзакций(Знач Количество) + Возврат ДождатьсяТранзакций("Начатые", Количество); +КонецФункции + +// Ждет, пока наблюдатель не увидит нужное число событий транзакций указанной фазы, не дольше 5 секунд. +// +// Параметры: +// Фаза - Строка - "Начатые" или "Завершенные": метод наблюдателя, которым отбираются события. +// Количество - Число - Сколько событий ждать. +// +// Возвращаемое значение: +// Массив из СобытиеИсточникаДанных - События транзакций, сколько бы их ни набралось к сроку. +// +Функция ДождатьсяТранзакций(Знач Фаза, Знач Количество) + + КрайнийСрок = ТекущаяУниверсальнаяДатаВМиллисекундах() + 5000; + + Пока Истина Цикл + Если Фаза = "Начатые" Тогда + Транзакции = Наблюдатель.Начатые("Транзакция"); + Иначе + Транзакции = Наблюдатель.Завершенные("Транзакция"); + КонецЕсли; + Если Транзакции.Количество() >= Количество ИЛИ ТекущаяУниверсальнаяДатаВМиллисекундах() > КрайнийСрок Тогда + Возврат Транзакции; + КонецЕсли; + Приостановить(20); + КонецЦикла; + +КонецФункции + +Процедура ДождатьсяЗадания(Задание) + Если НЕ Задание.ОжидатьЗавершения(30000) Тогда + ВызватьИсключение "Фоновое задание не завершилось за 30 секунд"; + КонецЕсли; +КонецПроцедуры + +// Колонка идентификатора ПростойОбъект строковая: без &Колонка тип колонки - Строка. +// +// Параметры: +// Идентификатор - Строка, Число - Идентификатор объекта, приводится к строке. +// +// Возвращаемое значение: +// ПростойОбъект - Объект с заполненными идентификатором и полем. +// +Функция НовыйПростойОбъект(Знач Идентификатор) + Объект = Новый ПростойОбъект; + Объект.Идентификатор = Строка(Идентификатор); + Объект.Поле = "поле " + Идентификатор; + Возврат Объект; +КонецФункции + +Процедура ПодключитьФикстуры() + + Фикстуры = Новый Массив; + Фикстуры.Добавить("ПростойОбъект"); + Фикстуры.Добавить("СущностьБезГенерируемогоИдентификатора"); + Фикстуры.Добавить("Автор"); + Фикстуры.Добавить("НаблюдательЗаписывающий"); + Фикстуры.Добавить("СигналСобытия"); + Фикстуры.Добавить("НаблюдательТормозящий"); + Фикстуры.Добавить("ПланЗакрытийКоннектора"); + Фикстуры.Добавить("НастройкиКоннектораТранзакций"); + Фикстуры.Добавить("КоннекторТранзакцийДляТестов"); + + Для Каждого Фикстура Из Фикстуры Цикл + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", Фикстура + ".os"), + Фикстура + ); + КонецЦикла; + +КонецПроцедуры diff --git "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" index e746521f..ff730ddc 100644 --- "a/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ "b/tests/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\265\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" @@ -217,6 +217,87 @@ КонецПроцедуры +// Пул сущностей держит по одному экземпляру на идентификатор, и сохранение обязано положить +// туда сохраненный объект. Возврат соединения к этому отношения не имеет: он идет после работы +// с СУБД и упасть может на закрытии непригодного соединения, но сохраненная сущность к тому +// моменту уже своя. +// +&Тест +Процедура НеудачноеОсвобождениеНеЛишаетСохраненнуюСущностьТождества() Экспорт + + ПодключитьФикстурыПадающегоЗакрытия(); + ХранилищеВПамяти.Очистить("тождество"); + + Настройки = Новый НастройкиКоннектораТранзакций; + + // Пул без предела соединений не держит и закрывает соединение на каждом возврате - только + // так неудачное закрытие попадает в освобождение соединения операции. + ИсточникТождества = Новый ИсточникДанных( + "тождество", + Тип("КоннекторТранзакцийДляТестов"), + "тождество", + Настройки.ПараметрыКоннектора(), + 0 + ); + ИсточникТождества.УстановитьАвтоЗакрытие(Ложь); + + Менеджер = ТестовыеУтилиты.МенеджерАвторов(ИсточникТождества); + + Попытка + + ХранилищеАвторов = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + // Настройки коннектор читает при открытии, поэтому план достается соединению, которое + // займет сохранение: закрытия инициализации уже позади и по плану не считаются. + Настройки.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + + Автор = Новый Автор; + Автор.Имя = "Пушкин"; + + Попытка + ХранилищеАвторов.Сохранить(Автор); + ОшибкаОсвобождения = Неопределено; + Исключение + ОшибкаОсвобождения = ИнформацияОбОшибке(); + КонецПопытки; + + Ожидаем.Что(ОшибкаОсвобождения, "Освобождение соединения падает на закрытии") + .Не_().Равно(Неопределено); + + Найденный = ХранилищеАвторов.ПолучитьОдно(Автор.ВнутреннийИдентификатор); + + Ожидаем.Что(Найденный = Автор, "Чтение отдает сохраненный экземпляр, а не второй на тот же идентификатор") + .ЭтоИстина(); + + Исключение + Менеджер.Закрыть(); + ИсточникТождества.Закрыть(); + ВызватьИсключение; + КонецПопытки; + + Менеджер.Закрыть(); + ИсточникТождества.Закрыть(); + +КонецПроцедуры + +// Подключает фикстуры коннектора, чье закрытие падает по плану. +// +Процедура ПодключитьФикстурыПадающегоЗакрытия() + + Фикстуры = Новый Массив; + Фикстуры.Добавить("ПланЗакрытийКоннектора"); + Фикстуры.Добавить("НастройкиКоннектораТранзакций"); + Фикстуры.Добавить("КоннекторТранзакцийДляТестов"); + + Для Каждого Фикстура Из Фикстуры Цикл + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", Фикстура + ".os"), + Фикстура + ); + КонецЦикла; + +КонецПроцедуры + // Создает источник и менеджер, работает с сущностью и закрывает их. // Процедура ПрожитьЦиклМенеджера()