diff --git a/README.md b/README.md index 026b341c..4b75acbf 100644 --- a/README.md +++ b/README.md @@ -491,7 +491,7 @@ ### МенеджерСущностей -МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который описывает тип *коннектора* к используемой базе данных, строку соединения и размер пула; создание менеджера напрямую по параметрам соединения устарело и сохранено для совместимости. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: +МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей создается из ИсточникДанных, который задает тип *коннектора* к используемой базе данных, строку соединения и размер пула и владеет пулом соединений; других способов создать менеджер нет. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся: * Создание и наполнение МоделиДанных * Трансляция запросов от прикладной логики к коннекторам @@ -501,11 +501,13 @@ ### ХранилищеСущностей -ХранилищеСущностей предоставляет тот же интерфейс по работе с сущностями и транзакциями, но с глобальной привязкой к конкретному типу сущности. Для получения ХранилищаСущностей служит метод `МенеджерСущностей::ПолучитьХранилищеСущностей`. +ХранилищеСущностей предоставляет тот же интерфейс по работе с сущностями и транзакциями, но с привязкой к конкретному типу сущности. Для получения ХранилищаСущностей служит метод `МенеджерСущностей::ПолучитьХранилищеСущностей`. В отличие от МенеджераСущностей, ХранилищеСущностей не требует передачи в методы параметра "ТипСущности". -Хранилища сущностей и пулы сущностей совпадают в рамках одного типа сущности, типа коннектора и строки соединения. Другими словами, два менеджера сущности, инициализированные одним и тем же коннектором и строкой соединения, вернут одинаковые хранилища сущностей одного типа. +Своей логики работы с БД у хранилища нет: оно подставляет свой тип сущности и делегирует операцию менеджеру, которому принадлежит. Поэтому поведение вызова через хранилище и через менеджер совпадает, в том числе для транзакций и прямого доступа к БД. + +Хранилища сущностей и пулы сущностей принадлежат менеджеру сущностей: хранилище работает с моделью данных того менеджера, который его создал, и закрывается вместе с ним. Один менеджер на один тип сущности всегда возвращает одно и то же хранилище, а два менеджера, созданные из одного источника данных, возвращают разные хранилища со своими пулами сущностей. Соединения при этом общие: их дает пул источника. @@ -528,7 +530,7 @@ > Важно! -Каждое ХранилищеСущностей и МенеджерСущностей хранят в себе отдельные экземпляры Коннекторов. Тип, строка соединения и параметры коннектора определяются при создании МенеджераСущностей. +Менеджеры и хранилища сущностей одного источника данных собственных коннекторов не держат: они берут соединения из общего пула этого источника. Тип, строка соединения и параметры коннектора задаются при создании ИсточникаДанных. @@ -578,7 +580,7 @@ Поэтому in-memory базу нельзя сочетать с **нулевым размером пула соединений**: при нулевом размере соединение открывается на операцию и закрывается сразу после нее, то есть между операциями открытых соединений не остается. Следующая операция откроет уже пустую базу и получит `no such table`. Переживет база такой промежуток или нет, зависит от внутреннего пулинга провайдера, то есть воспроизводится через раз. -Для in-memory базы используйте ненулевой размер пула — тогда пул придерживает соединения открытыми и база живет, пока жив менеджер сущностей. Если нулевой размер нужен принципиально, держите отдельное соединение к той же базе на все время работы: +Для in-memory базы используйте ненулевой размер пула — тогда пул придерживает соединения открытыми и база живет, пока не закрыт источник данных. Если нулевой размер нужен принципиально, держите отдельное соединение к той же базе на все время работы: ```bsl Держатель = Новый КоннекторSQLite; 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 c7e351db..f6c25745 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" @@ -1,10 +1,15 @@ ## ИсточникДанных -Описание источника данных: имя, тип коннектора, строка соединения, параметры коннектора -и размер пула соединений одним объектом. +Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула +и сам пул соединений одним объектом. -Источник соединений не открывает, только описывает их. Тип коннектора проверяется при -создании: тип без интерфейса `АбстрактныйКоннектор` отвергается с ошибкой. +Пул создается вместе с источником и общий для всех, кто работает через источник; соединения +пул открывает при первом обращении. Закрывает пул сам источник: методом `Закрыть()` или по +завершению потока, в котором источник создан, если автозакрытие не выключено, см. +`УстановитьАвтоЗакрытие`. Закрытие терминально: закрытый источник соединений больше не выдает, +поэтому любая операция через него завершается ошибкой. Тип коннектора и размер пула +проверяются при создании: тип без интерфейса `АбстрактныйКоннектор` и размер, не являющийся +неотрицательным числом, отвергаются с ошибкой. ```bsl Источник = Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения, , 20); @@ -84,3 +89,64 @@ // Функция РазмерПула() Экспорт ``` + +### УстановитьАвтоЗакрытие + +```bsl +// Включает или выключает закрытие источника по завершению потока, в котором он создан. +// +// По умолчанию включено. Выключает тот, кто управляет временем жизни источника сам: контейнер +// приложения или код, создающий источник в одном потоке для работы в других. Тогда источник +// закрывается только явным Закрыть(). +// +// Параметры: +// Значение - Булево - Истина - закрывать по завершению потока; Ложь - только явным Закрыть(). +// +Процедура УстановитьАвтоЗакрытие(Знач Значение) Экспорт +``` + +### АвтоЗакрытие + +```bsl +// Признак закрытия источника по завершению потока, см. УстановитьАвтоЗакрытие. +// +// Возвращаемое значение: +// Булево - Истина, если источник закрывается по завершению создавшего его потока. +// +Функция АвтоЗакрытие() Экспорт +``` + +### Закрыть + +```bsl +// Закрывает пул соединений источника: все его соединения закрываются. +// +// Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный +// вызов ничего не делает. Если закрытие завершилось ошибкой, источник закрытым не считается, +// и закрытие можно повторить: иначе оставшиеся соединения некому было бы закрыть. +// +Процедура Закрыть() Экспорт +``` + +### Закрыт + +```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 b7c1ff71..2e373405 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" @@ -5,27 +5,18 @@ ```bsl // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных: из него берутся тип коннектора, строка соединения, -// параметры коннектора и размер пула. Создание по параметрам соединения устарело и отмечается -// предупреждением в логе, как и параметры, переданные вместе с источником. +// Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его +// временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. -// - Тип - Устарел. Тип класса, реализующего интерфейс Коннектор. -// УстарелСтрокаСоединения - Строка - Устарел. Строка соединения к БД, задается в источнике данных. -// УстарелПараметрыКоннектора - Массив - Устарел. Параметры коннектора, задаются в источнике данных. -// УстарелРазмерПулаСоединений - Число - Устарел. Размер пула соединений, задается в источнике данных. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // -Процедура ПриСозданииОбъекта( - Знач ИсточникДанных, - Знач УстарелСтрокаСоединения = "", - Знач УстарелПараметрыКоннектора = Неопределено, - Знач УстарелРазмерПулаСоединений = 10 -) +Процедура ПриСозданииОбъекта(Знач ИсточникДанных) ``` ### ДобавитьКлассВМодель @@ -140,8 +131,9 @@ ### Закрыть ```bsl -// Выполняет очистку полную данных библиотеки. -// Дополнительно посылает всем используемым коннекторам запросы на закрытие соединения. +// Закрывает хранилища менеджера: после Закрыть() менеджер к работе непригоден. Закрытие +// терминально - у закрытого менеджера работают только геттеры и повторное закрытие, любой +// другой метод завершается ошибкой. // Процедура Закрыть() ``` @@ -210,11 +202,29 @@ Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Знач Объект = Неопределено) ``` +### ИсточникДанных + +```bsl +// Источник данных, из которого создан менеджер. +// +// Все обращения менеджера к БД идут через соединения этого источника. +// +// Возвращаемое значение: +// ИсточникДанных - Источник данных менеджера. +// +Функция ИсточникДанных() Экспорт +``` + ### ПолучитьХранилищеСущностей ```bsl // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // +// Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности +// менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается +// ошибкой, как и обращение за типом, не зарегистрированным в модели: такой тип можно +// зарегистрировать и получить хранилище снова. +// // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели // diff --git "a/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" "b/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" index a80411aa..0d428dd5 100644 --- "a/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" +++ "b/docs/\320\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.md" @@ -28,10 +28,3 @@ Функция СоздатьОбъектМодели(ТипСущности) Экспорт ``` -### Очистить - -```bsl - -Процедура Очистить() Экспорт -``` - diff --git "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" index b122c407..e8456ff4 100644 --- "a/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" +++ "b/docs/\320\237\320\276\321\202\320\276\320\272\320\276\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\260\321\217\320\240\320\260\320\261\320\276\321\202\320\260\320\241\320\221\320\224.md" @@ -43,7 +43,7 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" МенеджерСущностей.НачатьТранзакцию(); МенеджерСущностей.Сохранить(Сущность); -// Фоновое задание работает своим соединением и своей транзакцией +// Фоновое задание работает со своим соединением и своей транзакцией МенеджерСущностей.НачатьТранзакцию(); МенеджерСущностей.ЗафиксироватьТранзакцию(); // Записи основного потока не касается @@ -52,15 +52,17 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" ### Размер пула -Размер пула задается четвертым параметром конструктора и по умолчанию равен 10: +Размер пула задается пятым параметром конструктора `ИсточникДанных` и по умолчанию равен 10: ```bsl -МенеджерСущностей = Новый МенеджерСущностей( +Источник = Новый ИсточникДанных( + "Основной", Тип("КоннекторPostgreSQL"), "Host=localhost;Port=5432;Database=test", Неопределено, 10 ); +МенеджерСущностей = Новый МенеджерСущностей(Источник); ``` Размер - это предел параллельной работы с БД: он ограничивает число мест в пуле, а место занимает @@ -68,7 +70,7 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" следующий контекст встает в очередь; освобожденное соединение достается тому, кто ждет дольше всех. Ожидание в очереди ограничено по времени - по умолчанию 30 секунд. Не дождавшийся контекст -получает ошибку `Контекст не дождался свободного соединения за N мс`, а не висит бесконечно: +получает ошибку `Поток исполнения не дождался свободного соединения за N мс`, а не висит бесконечно: соединение мог не освободить контекст, который жив и продолжает работать, а у живого пул соединение не отбирает. @@ -82,11 +84,12 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" за каждое обращение платится установкой соединения. ```bsl -МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), СтрокаСоединения, Неопределено, 0); +Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения, , 0); ``` -Хранилища сущностей работают пулом своего менеджера, поэтому предел общий для менеджера. Пул - -обязательная зависимость хранилища: без менеджера хранилище не создается. +Пул принадлежит источнику данных: все менеджеры и хранилища сущностей одного источника делят +один пул, поэтому предел общий для источника. Хранилище своих соединений не держит: их дает +источник менеджера, которому хранилище принадлежит. ### Незавершенные транзакции @@ -139,9 +142,14 @@ Npgsql.PostgresException: 42601: syntax error at or near "INTO" ### Закрытие -`МенеджерСущностей.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. - -Закрытие терминально: после него пул соединений не выдает, и обращение к БД завершается ошибкой -`Пул соединений закрыт`. Иначе ожидающий, проснувшийся уже после закрытия, молча открыл бы себе -новое соединение, которое пережило бы закрытие пула, - и обещание «закрывает все соединения» -перестало бы выполняться. +`ИсточникДанных.Закрыть()` закрывает все соединения пула, в том числе занятые контекстами. Тот же +эффект дает завершение потока, в котором источник создан, если автозакрытие не выключено. +`МенеджерСущностей.Закрыть()` источник не трогает: менеджер его не создавал, а закрывает свои +хранилища. Хранилища других менеджеров того же источника это не затрагивает. + +Закрытие терминально на обоих уровнях. Закрытый источник соединений не выдает: обращение через +него завершается ошибкой `Источник данных <Имя> закрыт`. Закрытый пул тоже: ожидающий, +проснувшийся уже после закрытия, получает ошибку `Пул соединений закрыт`, а не открывает себе +молча новое соединение, которое пережило бы закрытие, - иначе обещание «закрывает все соединения» +перестало бы выполняться. Если закрытие завершилось ошибкой, источник закрытым не считается, и +закрытие можно повторить. diff --git "a/docs/\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.md" "b/docs/\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.md" index d1b08ac2..19dcbcaa 100644 --- "a/docs/\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.md" +++ "b/docs/\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.md" @@ -3,8 +3,15 @@ ### ПриСозданииОбъекта ```bsl - -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ТипКоннектора, Знач СтрокаСоединения, ПараметрыКоннектора) +// Создает хранилище, привязанное к типу сущности. +// +// Создается менеджером сущностей, напрямую конструировать не следует. +// +// Параметры: +// ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. +// ПМенеджер - МенеджерСущностей - Менеджер, создавший это хранилище. +// +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПМенеджер) ``` ### СоздатьЭлемент @@ -85,7 +92,8 @@ ```bsl // Выполняет очистку текущего Хранилища сущностей. -// Дополнительно посылает коннектору запрос на закрытие соединения. +// +// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. // Процедура Закрыть() Экспорт ``` @@ -93,7 +101,10 @@ ### НачатьТранзакцию ```bsl -// Посылает коннектору запрос на начало транзакции. +// Начинает транзакцию для текущего потока исполнения. +// +// Транзакция принадлежит потоку исполнения: в нее попадают все операции этого потока, а не только +// начатые через это хранилище. // Процедура НачатьТранзакцию() Экспорт ``` @@ -101,7 +112,7 @@ ### ЗафиксироватьТранзакцию ```bsl -// Посылает коннектору запрос на фиксацию транзакции. +// Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт ``` @@ -109,7 +120,7 @@ ### ОтменитьТранзакцию ```bsl -// Посылает коннектору запрос на отмену транзакции. +// Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт ``` diff --git a/packagedef b/packagedef index 022b5c4d..f59fecee 100644 --- a/packagedef +++ b/packagedef @@ -4,7 +4,7 @@ // // BSLLS:CodeOutOfRegion-off Описание.Имя("entity") - .Версия("4.4.1.0") + .Версия("5.4.0.0") .Автор("Nikita Fedkin") .АдресАвтора("nixel2007@gmail.com") .Описание("entity") @@ -17,14 +17,16 @@ .ВключитьФайл("LICENSE.md") .ЗависитОт("annotations", "1.6.0") .ЗависитОт("asserts", "1.6.2") + .ЗависитОт("atomic", "0.1.1") .ЗависитОт("collectionos", "0.8.3") .ЗависитОт("fluent", "0.6.1") .ЗависитОт("fs", "1.2.0") .ЗависитОт("lambdas", "0.3.4") .ЗависитОт("logos", "1.7.1") - .ЗависитОт("decorator", "2.0.8") + .ЗависитОт("decorator", "3.0.0") .ЗависитОт("reflector", "0.7.1") .ЗависитОт("sql", "1.3.3") .ЗависитОт("strings", "0.5.0") + .РазработкаЗависитОт("moskito", "1.0.0") .РазработкаЗависитОт("oneunit", "0.4.1") ; diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" new file mode 100644 index 00000000..f7906bb2 --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\321\202\320\276\320\272\320\270\320\230\321\201\320\277\320\276\320\273\320\275\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,15 @@ +// Поставщик текущего потока исполнения. +// +// Отдельный класс нужен, чтобы поток приходил к владельцу извне, а не из глобального контекста: +// тогда в тестах на его место становится поддельный поставщик и привязка "поток - ресурс" +// проверяется в одном потоке, без фоновых заданий и пауз. +// + +// Поток исполнения, в котором выполняется текущий код. +// +// Возвращаемое значение: +// ПотокИсполнения - Текущий поток исполнения. +// +Функция Текущий() Экспорт + Возврат ТекущийПоток(); +КонецФункции 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 85bd28dc..f8e9caa2 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" @@ -1,6 +1,7 @@ #Использовать "../concurrent" #Использовать collectionos +#Использовать logos // ПриоритетнаяОчередь - Очередь свободных соединений. Первым выдается возвращенное последним. // @@ -14,11 +15,11 @@ // Перем НомерПоследнейЗаявки; -// Соответствие - ПотокИсполнения исполнения и выданное ему соединение. +// Соответствие - Поток исполнения и выданное ему соединение. // Перем СоединенияПотоковИсполнения; -// Соответствие - ПотокИсполнения исполнения и сколько раз он занял соединение, не освободив. +// Соответствие - Поток исполнения и сколько раз он занял соединение, не освободив. // Перем ГлубиныЗахвата; @@ -34,7 +35,7 @@ // Перем СозданоСоединений; -// Соответствие - ПотокиИсполнения, которые прямо сейчас открывают себе соединение. Место в пуле +// Соответствие - Потоки исполнения, которые прямо сейчас открывают себе соединение. Место в пуле // за ними уже занято, а самого соединения еще нет: открытие идет вне блокировки. // Перем Брони; @@ -63,6 +64,16 @@ // Перем ПараметрыКоннектора; +// ПотокиИсполнения - Поставщик текущего потока исполнения. Пул спрашивает поток у него, а не у +// глобального контекста: в тестах на его место становится поддельный поставщик. +// +Перем Потоки; + +// Лог - Журнал пула: в него уходит ошибка освобождения ресурсов завершившегося потока исполнения, +// жаловаться о которой уже некому. +// +Перем Лог; + // Пул соединений с базой данных. // // Через пул идут все обращения к БД. Соединение выдается потоку исполнения на время операции @@ -98,6 +109,7 @@ // ППараметрыКоннектора - Массив - Дополнительные параметры коннектора. // ППределОжидания - Число - Предел ожидания соединения в очереди, в миллисекундах. // ППаузаОжидания - Число - Пауза между проверками своей заявки ожидающим, в миллисекундах. +// ППотоки - ПотокиИсполнения - Поставщик текущего потока исполнения. По умолчанию настоящий. // Процедура ПриСозданииОбъекта( Знач ПРазмерПула, @@ -105,7 +117,8 @@ Знач ПСтрокаСоединения, Знач ППараметрыКоннектора, Знач ППределОжидания = 30000, - Знач ППаузаОжидания = 10 + Знач ППаузаОжидания = 10, + Знач ППотоки = Неопределено ) // Отрицательный размер не отвергает ни один захват явно: предел просто никогда не позволяет @@ -117,10 +130,13 @@ ); КонецЕсли; + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.pool"); + РазмерПула = ПРазмерПула; ТипКоннектора = ПТипКоннектора; СтрокаСоединения = ПСтрокаСоединения; ПараметрыКоннектора = ППараметрыКоннектора; + Потоки = ?(ППотоки = Неопределено, Новый ПотокиИсполнения(), ППотоки); // Возвращенное позже считается меньшим, поэтому оказывается в голове очереди. Свободные = Новый ПриоритетнаяОчередь( @@ -159,19 +175,28 @@ // BSLLS:UnusedLocalMethod-off Процедура ПриЗавершенииПотокаИсполнения() - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Запись().Заблокировать(); + // Подписчиков у завершения потока столько, сколько пулов и источников его застали, и движок + // обходит их одним циклом, не перехватывая ошибки обработчиков. Выпусти этот обработчик + // исключение, и до подписавшихся позже очередь не дойдет: их соединения так и остались бы + // за завершившимся потоком. Ошибка уходит в лог: поток кончился, и сообщать о ней некому. Попытка ОсвободитьРесурсыПотокаИсполнения(ПотокИсполнения); + ОшибкаОсвобождения = ""; Исключение - Блокировка.Запись().Разблокировать(); - ВызватьИсключение; + ОшибкаОсвобождения = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); КонецПопытки; Блокировка.Запись().Разблокировать(); + // Запись в лог - вне блокировки: держать ее ради журнала незачем. + Если НЕ ПустаяСтрока(ОшибкаОсвобождения) Тогда + Лог.Ошибка("Не удалось освободить ресурсы завершившегося потока исполнения: %1", ОшибкаОсвобождения); + КонецЕсли; + КонецПроцедуры // BSLLS:UnusedLocalMethod-on @@ -188,9 +213,9 @@ // Функция Занять() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); - // ПотокИсполнения может не дойти до Освободить(): оборваться, забыть освободить или просто + // Поток исполнения может не дойти до Освободить(): оборваться, забыть освободить или просто // закончиться. Подписка снимает с пула обязанность выяснять это опросом - о завершении // сообщит сам поток исполнения. Повторная подписка тем же обработчиком ничего не добавляет. ДобавитьОбработчик ПотокИсполнения.ПриЗавершении, ПриЗавершенииПотокаИсполнения; @@ -228,7 +253,7 @@ // Процедура Освободить() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Запись().Заблокировать(); @@ -254,7 +279,7 @@ // Функция СоединениеТекущегоПотокаИсполнения() Экспорт - ПотокИсполнения = ТекущийПоток(); + ПотокИсполнения = Потоки.Текущий(); Блокировка.Чтение().Заблокировать(); Результат = СоединенияПотоковИсполнения.Получить(ПотокИсполнения); @@ -343,42 +368,6 @@ КонецФункции -// Возвращает тип коннектора, которым пул открывает соединения. -// -// Возвращаемое значение: -// Тип - Тип класса, реализующего интерфейс коннектора. -// -Функция ТипКоннектора() Экспорт - Возврат ТипКоннектора; -КонецФункции - -// Возвращает строку соединения, с которой пул открывает соединения. -// -// Возвращаемое значение: -// Строка - Строка соединения с БД. -// -Функция СтрокаСоединения() Экспорт - Возврат СтрокаСоединения; -КонецФункции - -// Возвращает дополнительные параметры коннекторов пула. -// -// Возвращаемое значение: -// Массив - Параметры коннектора. -// -Функция ПараметрыКоннектора() Экспорт - Возврат ПараметрыКоннектора; -КонецФункции - -// Возвращает размер пула. -// -// Возвращаемое значение: -// Число - Предел одновременно живущих соединений; 0 - без предела. -// -Функция РазмерПула() Экспорт - Возврат РазмерПула; -КонецФункции - // Закрывает все соединения пула. // // Закрытие терминально: после него пул соединений не выдает, и `Занять()` завершается ошибкой. @@ -391,29 +380,45 @@ Попытка - Для Каждого КлючИЗначение Из СоединенияПотоковИсполнения Цикл - КлючИЗначение.Значение.Закрыть(); + // Соединение выбывает со своего места - из соответствия, из очереди свободных, из заявки - + // после того, как закрыто, и тут же возвращает пулу занятое им место. Споткнись закрытие + // на соединении, оно остается там, где лежало: его состояние неизвестно, и повтор должен + // попробовать снова. Изыми его раньше - и повтор прошел бы мимо, отчитался об успехе, а + // соединение с БД осталось бы открытым. Уже закрытые при этом никому не достанутся, а + // освобожденные места не заставят следующий захват ждать того, чего не будет. + Для Каждого ПотокИсполнения Из ПотокиСЗанятымиСоединениями() Цикл + СоединенияПотоковИсполнения.Получить(ПотокИсполнения).Закрыть(); + СоединенияПотоковИсполнения.Удалить(ПотокИсполнения); + ГлубиныЗахвата.Удалить(ПотокИсполнения); + СозданоСоединений = СозданоСоединений - 1; КонецЦикла; - СоединенияПотоковИсполнения.Очистить(); - ГлубиныЗахвата.Очистить(); Пока Свободные.Количество() > 0 Цикл - Свободные.Взять().Получить().Закрыть(); + Свободные.Подсмотреть().Получить().Закрыть(); + Свободные.Взять(); + СозданоСоединений = СозданоСоединений - 1; КонецЦикла; // Соединение, уже отданное заявке, не лежит ни за потоком, ни в свободных: заберет его // ожидающий, до которого очередь дошла. Без разбора заявок оно пережило бы закрытие пула. + // + // Соединение снятой заявки уже досталось своему потоку исполнения и закрыто вместе с + // остальными занятыми: сама заявка держится в очереди только до ближайшей ее уборки. Такую + // заявку закрытие пропускает - иначе одно соединение сосчиталось бы дважды. Пока Ожидающие.Количество() > 0 Цикл - Заявка = Ожидающие.Взять().Получить(); - Заявка.Снята = Истина; - Если Заявка.Соединение <> Неопределено Тогда + Заявка = Ожидающие.Подсмотреть().Получить(); + Если НЕ Заявка.Снята И Заявка.Соединение <> Неопределено Тогда Заявка.Соединение.Закрыть(); Заявка.Соединение = Неопределено; + СозданоСоединений = СозданоСоединений - 1; КонецЕсли; + Заявка.Снята = Истина; + Ожидающие.Взять(); КонецЦикла; // Брони снимаются последними: по снятой броне открывающий поток исполнения поймет, что пул - // закрыт, и закроет свое соединение сам - здесь его еще нет. + // закрыт, и закроет свое соединение сам - здесь его еще нет. Их места пулу возвращает + // обнуление счетчика: закрытые соединения свои вернули уже сами. Брони.Очистить(); СозданоСоединений = 0; @@ -431,6 +436,25 @@ КонецПроцедуры +// Потоки исполнения, за которыми сейчас закреплены соединения, отдельным массивом. +// +// По самому соответствию идти нельзя: записи удаляются прямо в цикле закрытия. +// +// Возвращаемое значение: +// Массив - Потоки исполнения с занятыми соединениями. +// +Функция ПотокиСЗанятымиСоединениями() + + Результат = Новый Массив(); + + Для Каждого КлючИЗначение Из СоединенияПотоковИсполнения Цикл + Результат.Добавить(КлючИЗначение.Ключ); + КонецЦикла; + + Возврат Результат; + +КонецФункции + // Одна попытка занять соединение. Вызывается при захваченной блокировке записи. // // Параметры: @@ -703,7 +727,7 @@ Блокировка.Запись().Разблокировать(); ВызватьИсключение СтрШаблон( - "ПотокИсполнения не дождался свободного соединения за %1 мс", + "Поток исполнения не дождался свободного соединения за %1 мс", Формат(ПределОжидания, "ЧГ=") ); @@ -827,26 +851,33 @@ // Процедура ВернутьВПул(Соединение) + // Возврат откатывает транзакцию, которую поток исполнения не завершил сам, и о пригодности + // соединения до этого судить рано: неудачный откат делает соединение непригодным. + Соединение.ОтметитьВозврат(); + // Соединение, на котором не удалось завершить транзакцию, переиспользовать нельзя: в каком // состоянии осталась транзакция, неизвестно. Такое соединение закрывается, а его место // возвращается в пул - следующий поток исполнения откроет себе новое. + // + // Место возвращается до закрытия: соединение уже отобрано у потока исполнения и пулу больше + // не видно, поэтому упади закрытие после уменьшения счетчика - место так и осталось бы + // занятым соединением, которого нет. Если Соединение.Испорчено() Тогда - Соединение.Закрыть(); СозданоСоединений = СозданоСоединений - 1; + Соединение.Закрыть(); Возврат; КонецЕсли; - Соединение.ОтметитьВозврат(); - Заявка = ПервыйОжидающий(); Если Заявка <> Неопределено Тогда Заявка.Соединение = Соединение; Возврат; КонецЕсли; + // Пул без предела соединений не держит: место возвращается так же, до закрытия. Если РазмерПула = 0 Тогда - Соединение.Закрыть(); СозданоСоединений = СозданоСоединений - 1; + Соединение.Закрыть(); Возврат; КонецЕсли; 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 52bd020a..0b93c0d1 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" @@ -130,8 +130,9 @@ // Отмечает возврат соединения в пул. // // Незавершенная транзакция при этом откатывается: соединение уходит следующему потоку, и -// оставлять на нем чужую начатую транзакцию нельзя. Ошибка отката подавляется - соединение -// возвращается в пул в любом случае, иначе оно было бы потеряно. +// оставлять на нем чужую начатую транзакцию нельзя. Ошибка отката наружу не идет - соединение +// возвращается в пул в любом случае, иначе оно было бы потеряно, - но соединение после нее +// непригодно так же, как после неудачного явного завершения транзакции. // Процедура ОтметитьВозврат() Экспорт @@ -139,7 +140,7 @@ Попытка ЭтотОбъект.ОтменитьТранзакцию(); Исключение - ВТранзакции = Ложь; + ИспортитьПослеНеудачногоЗавершения(); КонецПопытки; КонецЕсли; diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" index 7032f549..42ad7002 100644 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" +++ "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\220\320\272\321\202\320\270\320\262\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214.os" @@ -1,76 +1,54 @@ -#Использовать decorator - -Функция ТипСущности(Сущность) Экспорт - ТипСущности = ТипЗнч(Сущность); - Если ТипСущности = Тип("Сценарий") Тогда - ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность); - КонецЕсли; - - Возврат ТипСущности; -КонецФункции - -Функция СоздатьИзМенеджера(ОбъектМодели, МенеджерСущностей) Экспорт - Сущность = Новый(ОбъектМодели.ТипСущности()); - - Декоратор = Новый ПостроительДекоратора(Сущность) - .Импорт(Новый Импорт("decorator")) - - .Поле(Новый Поле("_МенеджерСущностей") - .ЗначениеПоУмолчанию(МенеджерСущностей)) - - .Поле(Новый Поле("_ОбъектМодели") - .ЗначениеПоУмолчанию(ОбъектМодели)) - - .Метод(Новый Метод("Прочитать") - .Публичный() - .ТелоМетода("_ТипСущности = ОбработкаДекоратора.ИсходныйТип(ЭтотОбъект); - |_ДанныеСущности = _МенеджерСущностей.ПолучитьОдно( - | _ТипСущности, - | _ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект) - |); - |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) - - - .Метод(Новый Метод("Сохранить") - .Публичный() - .ТелоМетода("_МенеджерСущностей.Сохранить(ЭтотОбъект);")) - - .Метод(Новый Метод("Удалить") - .Публичный() - .ТелоМетода("_МенеджерСущностей.Удалить(ЭтотОбъект);")) - - .Построить(); - - Возврат Декоратор; -КонецФункции - -Функция СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей) Экспорт - Сущность = Новый(ОбъектМодели.ТипСущности()); - - Декоратор = Новый ПостроительДекоратора(Сущность) - .Импорт(Новый Импорт("decorator")) - - .Поле(Новый Поле("_ХранилищеСущностей") - .ЗначениеПоУмолчанию(ХранилищеСущностей)) - - .Поле(Новый Поле("_ОбъектМодели") - .ЗначениеПоУмолчанию(ОбъектМодели)) - - .Метод(Новый Метод("Прочитать") - .Публичный() - .ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект)); - |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) - - - .Метод(Новый Метод("Сохранить") - .Публичный() - .ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);")) - - .Метод(Новый Метод("Удалить") - .Публичный() - .ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);")) - - .Построить(); - - Возврат Декоратор; -КонецФункции \ No newline at end of file +#Использовать decorator + +Функция ТипСущности(Сущность) Экспорт + ТипСущности = ТипЗнч(Сущность); + Если ТипСущности = Тип("Сценарий") Тогда + ТипСущности = ОбработкаДекоратора.ИсходныйТип(Сущность); + КонецЕсли; + + Возврат ТипСущности; +КонецФункции + +// Создает экземпляр сущности, расширенный методами паттерна Active Record. +// +// Запись работает через хранилище своего типа, кто бы ее ни создал: хранилище тип уже знает, +// поэтому в методы записи его передавать не нужно, а операцию хранилище все равно делегирует +// своему менеджеру. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель сущности, экземпляр которой создается. +// ХранилищеСущностей - ХранилищеСущностей - Хранилище типа этой сущности. +// +// Возвращаемое значение: +// Произвольный - Экземпляр класса сущности с методами Прочитать, Сохранить и Удалить. +// +Функция Создать(ОбъектМодели, ХранилищеСущностей) Экспорт + Сущность = Новый(ОбъектМодели.ТипСущности()); + + Декоратор = Новый ПостроительДекоратора(Сущность) + .Импорт(Новый Импорт("decorator")) + + .Поле(Новый Поле("_ХранилищеСущностей") + .ЗначениеПоУмолчанию(ХранилищеСущностей)) + + .Поле(Новый Поле("_ОбъектМодели") + .ЗначениеПоУмолчанию(ОбъектМодели)) + + .Метод(Новый Метод("Прочитать") + .Публичный() + .ТелоМетода("_ДанныеСущности = _ХранилищеСущностей.ПолучитьОдно(_ОбъектМодели.ПолучитьЗначениеИдентификатора(ЭтотОбъект)); + |ОбработкаДекоратора.СинхронизироватьПоля(_ДанныеСущности, ЭтотОбъект);")) + + + .Метод(Новый Метод("Сохранить") + .Публичный() + .ТелоМетода("_ХранилищеСущностей.Сохранить(ЭтотОбъект);")) + + .Метод(Новый Метод("Удалить") + .Публичный() + .ТелоМетода("_ХранилищеСущностей.Удалить(ЭтотОбъект);")) + + .Построить(); + + Возврат Декоратор; +КонецФункции 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 1fbfdd66..17b0ec33 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" @@ -17,20 +17,26 @@ КонецЕсли; КонецПроцедуры -// Сохраняет сущность в БД соединением из пула. +// Сохраняет сущность в БД соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Сохраняемая сущность. // -Процедура СохранитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Сохранить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт + ПроверитьЧтоКлассЯвляетсяСущностью(АктивнаяЗапись.ТипСущности(Сущность)); + ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); + + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка - Сохранить(Соединение.ПолучитьКоннектор(), ОбъектМодели, ПулСущностей, Сущность); + Коннектор = Соединение.ПолучитьКоннектор(); + Коннектор.Сохранить(ОбъектМодели, Сущность); + СохранитьПодчиненныеТаблицы(Коннектор, ОбъектМодели, Сущность); Исключение Пул.Освободить(); ВызватьИсключение; @@ -38,25 +44,38 @@ Пул.Освободить(); + ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); + КонецПроцедуры -// Ищет сущности соединением из пула. +// Ищет сущности соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// ОпцииПоиска - ОпцииПоиска - Опции поиска. +// ОпцииПоиска - Соответствие, ОпцииПоиска - Опции поиска. // // Возвращаемое значение: // Массив - Найденные сущности. // -Функция ПолучитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция Получить(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт + ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(ОбъектМодели.Колонки(), ОпцииПоиска); + НайденныеСущности = Новый Массив; + + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка - Результат = Получить(Соединение.ПолучитьКоннектор(), Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска); + + Коннектор = Соединение.ПолучитьКоннектор(); + НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); + + Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл + НайденныеСущности.Добавить(СобратьСущность(Коннектор, Менеджер, ОбъектМодели, ПулСущностей, НайденнаяСтрока)); + КонецЦикла; + Исключение Пул.Освободить(); ВызватьИсключение; @@ -64,53 +83,57 @@ Пул.Освободить(); - Возврат Результат; + Возврат НайденныеСущности; КонецФункции -// Ищет одну сущность соединением из пула. +// Ищет одну сущность соединением источника данных. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// ОпцииПоиска - ОпцииПоиска - Опции поиска. +// ОпцииПоиска - Соответствие, ОпцииПоиска, Произвольный - Опции поиска; любой другой тип - идентификатор. // // Возвращаемое значение: // Произвольный - Найденная сущность. // Неопределено - Сущность не найдена. // -Функция ПолучитьОдноЧерезПул(Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт +Функция ПолучитьОдно(Менеджер, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - Соединение = Пул.Занять(); - - Попытка - Результат = ПолучитьОдно(Соединение.ПолучитьКоннектор(), Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска); - Исключение - Пул.Освободить(); - ВызватьИсключение; - КонецПопытки; + Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") ИЛИ ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда + ПередаваемыеОпцииПоиска = ОпцииПоиска; + Иначе + ПередаваемыеОпцииПоиска = Новый ОпцииПоиска(); + ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); + КонецЕсли; - Пул.Освободить(); + НайденныеСущности = Получить(Менеджер, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); - Возврат Результат; + Если НайденныеСущности.Количество() = 0 Тогда + Возврат Неопределено; + Иначе + Возврат НайденныеСущности[0]; + КонецЕсли; КонецФункции -// Удаляет сущность из БД соединением из пула. +// Удаляет сущность из БД соединением источника данных. +// Сущность должна иметь заполненный идентификатор. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища своих типов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // Сущность - Произвольный - Удаляемая сущность. // -Процедура УдалитьЧерезПул(Пул, ОбъектМодели, ПулСущностей, Сущность) Экспорт +Процедура Удалить(Менеджер, ОбъектМодели, ПулСущностей, Сущность) Экспорт + Пул = Менеджер.ИсточникДанных().ПулСоединений(); Соединение = Пул.Занять(); Попытка - Удалить(Соединение.ПолучитьКоннектор(), ОбъектМодели, ПулСущностей, Сущность); + Соединение.ПолучитьКоннектор().Удалить(ОбъектМодели, Сущность); Исключение Пул.Освободить(); ВызватьИсключение; @@ -118,23 +141,27 @@ Пул.Освободить(); + // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы ничего. + ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); + КонецПроцедуры -// Выполняет алгоритм коннектором из пула и возвращает соединение обратно. +// Выполняет алгоритм коннектором источника данных и возвращает соединение обратно. // // Соединение занимается на время выполнения алгоритма. Если текущий поток исполнения уже // начал транзакцию, алгоритм получает коннектор этой транзакции и попадает в нее. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется алгоритм. // Алгоритм - Строка - Лямбда-выражение с одним параметром - коннектором. // Контекст - Структура - Значения, доступные в алгоритме по именам ключей. // Объект - Сценарий - Объект, поля и методы которого доступны в алгоритме. // -Процедура ВыполнитьСКоннекторомЧерезПул(Пул, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт +Процедура ВыполнитьСКоннектором(Источник, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниПроцедура()); + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -148,13 +175,14 @@ КонецПроцедуры -// Вычисляет значение алгоритмом, выполненным коннектором из пула, и возвращает соединение обратно. +// Вычисляет значение алгоритмом, выполненным коннектором источника данных, и возвращает +// соединение обратно. // // Соединение занимается на время выполнения алгоритма. Если текущий поток исполнения уже // начал транзакцию, алгоритм получает коннектор этой транзакции и попадает в нее. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого выполняется алгоритм. // Алгоритм - Строка - Лямбда-выражение с одним параметром - коннектором. // Контекст - Структура - Значения, доступные в алгоритме по именам ключей. // Объект - Сценарий - Объект, поля и методы которого доступны в алгоритме. @@ -162,10 +190,11 @@ // Возвращаемое значение: // Произвольный - Результат алгоритма. // -Функция ВычислитьСКоннекторомЧерезПул(Пул, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт +Функция ВычислитьСКоннектором(Источник, Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт Действие = ПостроитьДействие(Алгоритм, Контекст, Объект, ФункциональныеИнтерфейсы.УниФункция()); + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -214,10 +243,11 @@ // попадают в эту транзакцию, а завершить ее другой поток не может. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого берется соединение. +// Источник - ИсточникДанных - Источник, соединением которого открывается транзакция. // -Процедура НачатьТранзакциюПотокаИсполнения(Пул) Экспорт +Процедура НачатьТранзакциюПотокаИсполнения(Источник) Экспорт + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -236,19 +266,19 @@ // Фиксирует транзакцию текущего потока исполнения и возвращает соединение в пул. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // -Процедура ЗафиксироватьТранзакциюПотокаИсполнения(Пул) Экспорт - ЗавершитьТранзакциюПотокаИсполнения(Пул, Истина); +Процедура ЗафиксироватьТранзакциюПотокаИсполнения(Источник) Экспорт + ЗавершитьТранзакциюПотокаИсполнения(Источник, Истина); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает соединение в пул. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // -Процедура ОтменитьТранзакциюПотокаИсполнения(Пул) Экспорт - ЗавершитьТранзакциюПотокаИсполнения(Пул, Ложь); +Процедура ОтменитьТранзакциюПотокаИсполнения(Источник) Экспорт + ЗавершитьТранзакциюПотокаИсполнения(Источник, Ложь); КонецПроцедуры // Завершает транзакцию текущего потока и освобождает его соединение. @@ -262,11 +292,12 @@ // тем более незачем. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // Фиксировать - Булево - Истина - зафиксировать транзакцию, Ложь - отменить. // -Процедура ЗавершитьТранзакциюПотокаИсполнения(Пул, Знач Фиксировать) +Процедура ЗавершитьТранзакциюПотокаИсполнения(Источник, Знач Фиксировать) + Пул = Источник.ПулСоединений(); Соединение = Пул.Занять(); Попытка @@ -284,76 +315,75 @@ КонецПроцедуры -Процедура Сохранить(Коннектор, ОбъектМодели, ПулСущностей, Сущность) Экспорт - ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); +// Перезаписывает строки подчиненных таблиц сущности: старые строки удаляются, текущее +// содержимое коллекций сохраняется заново. +// +// Параметры: +// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// Сущность - Произвольный - Сохраняемая сущность. +// +Процедура СохранитьПодчиненныеТаблицы(Коннектор, ОбъектМодели, Сущность) + + Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл - ПроверитьЧтоКлассЯвляетсяСущностью(ТипСущности); - ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); - ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность); - - Коннектор.Сохранить(ОбъектМодели, Сущность); - - // Сохранение полей, расширяемых таблицами - ПодчиненныеТаблицы = ОбъектМодели.ПодчиненныеТаблицы(); - Для Каждого ПодчиненнаяТаблица Из ПодчиненныеТаблицы Цикл - ОбъектМоделиЭлементКоллекции = ОбработкаКоллекций.ПолучитьОбъектМоделиДляПодчиненнойТаблицы( ОбъектМодели, ПодчиненнаяТаблица ); - + // Очистка подчиненной таблицы ОпцииПоиска = Новый ОпцииПоиска(); ОпцииПоиска.Отбор("ref", ВидСравнения.Равно, ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); Коннектор.УдалитьСтрокиВТаблице(ОбъектМоделиЭлементКоллекции, ОпцииПоиска); - + РасширяемоеПоле = ОбъектМодели.ПолучитьЗначениеПоля(Сущность, ПодчиненнаяТаблица.ИмяПоля); - + Если НЕ ЗначениеЗаполнено(РасширяемоеПоле) Тогда Продолжить; КонецЕсли; - + КоллекцияСущностей = ОбработкаКоллекций.ПреобразоватьКоллекциюККоллекцииСущностей( РасширяемоеПоле, Сущность, ОбъектМодели, ПодчиненнаяТаблица ); - + Для Каждого ЭлементКоллекции Из КоллекцияСущностей Цикл Коннектор.Сохранить(ОбъектМоделиЭлементКоллекции, ЭлементКоллекции); КонецЦикла; - + КонецЦикла; - ПулСущностей.Вставить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность), Сущность); КонецПроцедуры -Функция Получить(Коннектор, Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - - Колонки = ОбъектМодели.Колонки(); - ПередаваемыеОпцииПоиска = ПреобразоватьОпцииПоиска(Колонки, ОпцииПоиска); - - НайденныеСущности = Новый Массив; - НайденныеСтроки = Коннектор.НайтиСтрокиВТаблице(ОбъектМодели, ПередаваемыеОпцииПоиска); - - Для Каждого НайденнаяСтрока Из НайденныеСтроки Цикл - - ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( - НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), - ОбъектМодели.Идентификатор().ИмяПоля - ); - - Сущность = ПолучитьСущностьИзПула(Пул, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); +// Собирает сущность по прочитанной строке таблицы: берет экземпляр из пула сущностей, +// заполняет колонки и подчиненные таблицы. +// +// Параметры: +// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища по ссылкам. +// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. +// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. +// НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. +// +// Возвращаемое значение: +// Произвольный - Заполненная сущность. +// +Функция СобратьСущность(Коннектор, Менеджер, ОбъектМодели, ПулСущностей, НайденнаяСтрока) - ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Пул, ОбъектМодели, ЗначениеИдентификатора); - ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Пул, ОбъектМодели, ЗначениеИдентификатора); + ЗначениеИдентификатора = ОбъектМодели.ПривестиЗначениеПоля( + НайденнаяСтрока.Получить(ОбъектМодели.Идентификатор().ИмяКолонки), + ОбъектМодели.Идентификатор().ИмяПоля + ); - НайденныеСущности.Добавить(Сущность); + Сущность = ПолучитьСущностьИзПула(Менеджер, ОбъектМодели, ПулСущностей, ЗначениеИдентификатора); - КонецЦикла; + ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Менеджер, ОбъектМодели, ЗначениеИдентификатора); + ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Менеджер, ОбъектМодели, ЗначениеИдентификатора); - Возврат НайденныеСущности; + Возврат Сущность; КонецФункции @@ -426,7 +456,7 @@ // Возвращает экземпляр сущности из пула сущностей, создавая его при первом обращении. // // Параметры: -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Менеджер - МенеджерСущностей - Менеджер, которому принадлежит хранилище сущности. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. // ЗначениеИдентификатора - Произвольный - Идентификатор сущности. @@ -434,15 +464,15 @@ // Возвращаемое значение: // Произвольный - Экземпляр сущности. // -Функция ПолучитьСущностьИзПула(Пул, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) +Функция ПолучитьСущностьИзПула(Менеджер, ОбъектМодели, ПулСущностей, Знач ЗначениеИдентификатора) Сущность = ПулСущностей.ПолучитьИлиУмолчание(ЗначениеИдентификатора, Неопределено); Если Сущность <> Неопределено Тогда Возврат Сущность; КонецЕсли; - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, Пул); - Сущность = АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ХранилищеСущностей); + ХранилищеСущностей = Менеджер.ПолучитьХранилищеСущностей(ОбъектМодели.ТипСущности()); + Сущность = АктивнаяЗапись.Создать(ОбъектМодели, ХранилищеСущностей); // Пул мог пополниться, пока создавалась сущность: экземпляр на идентификатор должен остаться // один, иначе разные потоки получат разные объекты одной строки. Победителем остается тот, @@ -458,11 +488,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // НайденнаяСтрока - Соответствие - Прочитанная строка таблицы. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Менеджер - МенеджерСущностей - Менеджер операции: дает источник и хранилища по ссылкам. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Пул, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьКолонки(Сущность, НайденнаяСтрока, Менеджер, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого Колонка Из ОбъектМодели.Колонки() Цикл @@ -472,7 +502,7 @@ ЗначениеКолонки = РазыменоватьСсылку( ЗначениеКолонки, Колонка.ТипСсылки, - Пул, + Менеджер, ОбъектМодели, Сущность, ЗначениеИдентификатора @@ -493,7 +523,7 @@ // Параметры: // ЗначениеКолонки - Произвольный - Идентификатор сущности, на которую указывает ссылка. // ТипСсылки - Тип - Тип сущности, на которую указывает ссылка. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашивается хранилище ссылки. // ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит колонка. // Сущность - Произвольный - Сущность, которой принадлежит колонка. // ЗначениеИдентификатора - Произвольный - Идентификатор этой сущности. @@ -504,7 +534,7 @@ Функция РазыменоватьСсылку( Знач ЗначениеКолонки, Знач ТипСсылки, - Пул, + Менеджер, ОбъектМодели, Сущность, Знач ЗначениеИдентификатора @@ -514,7 +544,7 @@ Возврат Сущность; КонецЕсли; - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипСсылки), Пул); + ХранилищеСущностейСсылки = Менеджер.ПолучитьХранилищеСущностей(ТипСсылки); Возврат ХранилищеСущностейСсылки.ПолучитьОдно(ЗначениеКолонки); @@ -525,11 +555,11 @@ // Параметры: // Сущность - Произвольный - Заполняемая сущность. // Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашиваются хранилища элементов. // ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. // ЗначениеИдентификатора - Произвольный - Идентификатор заполняемой сущности. // -Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Пул, ОбъектМодели, Знач ЗначениеИдентификатора) +Процедура ЗаполнитьПодчиненныеТаблицы(Сущность, Коннектор, Менеджер, ОбъектМодели, Знач ЗначениеИдентификатора) Для Каждого ПодчиненнаяТаблица Из ОбъектМодели.ПодчиненныеТаблицы() Цикл @@ -545,7 +575,7 @@ ЗначениеКолонки = ОбработкаКоллекций.ПреобразоватьКоллекциюСтрокККоллекции(Строки, ПодчиненнаяТаблица); Если ПодчиненнаяТаблица.КаскадноеЧтение И ТипыКолонок.ЭтоСсылочныйТип(ПодчиненнаяТаблица.ТипЭлемента) Тогда - ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Пул, ОбъектМодели); + ПрочитатьЭлементыКоллекции(ЗначениеКолонки, ПодчиненнаяТаблица.ТипЭлемента, Менеджер); КонецЕсли; ОбъектМодели.УстановитьЗначениеПодчиненнойТаблицыВПоле(Сущность, ПодчиненнаяТаблица.ИмяПоля, ЗначениеКолонки); @@ -559,12 +589,11 @@ // Параметры: // Коллекция - Массив - Коллекция идентификаторов, изменяется на месте. // ТипЭлемента - Тип - Тип сущности элементов коллекции. -// Пул - ПулСоединенийСБД - Пул,из которого взято соединение. -// ОбъектМодели - ОбъектМодели - Модель сущности, которой принадлежит коллекция. +// Менеджер - МенеджерСущностей - Менеджер, у которого запрашивается хранилище элементов. // -Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Пул, ОбъектМодели) +Процедура ПрочитатьЭлементыКоллекции(Коллекция, Знач ТипЭлемента, Менеджер) - ХранилищеСущностейСсылки = ХранилищаСущностей.Получить(ОбъектМодели.МодельДанных().Получить(ТипЭлемента), Пул); + ХранилищеСущностейСсылки = Менеджер.ПолучитьХранилищеСущностей(ТипЭлемента); Для НомерИндекса = 0 По Коллекция.ВГраница() Цикл Коллекция[НомерИндекса] = ХранилищеСущностейСсылки.ПолучитьОдно(Коллекция[НомерИндекса]); @@ -572,42 +601,6 @@ КонецПроцедуры -Функция ПолучитьОдно(Коннектор, Пул, ОбъектМодели, ПулСущностей, ОпцииПоиска) Экспорт - - Если ТипЗнч(ОпцииПоиска) = Тип("Соответствие") Тогда - ПередаваемыеОпцииПоиска = ОпцииПоиска; - ИначеЕсли ТипЗнч(ОпцииПоиска) = Тип("ОпцииПоиска") Тогда - ПередаваемыеОпцииПоиска = ОпцииПоиска; - Иначе - ПередаваемыеОпцииПоиска = Новый ОпцииПоиска(); - ПередаваемыеОпцииПоиска.Отбор(ОбъектМодели.Идентификатор().ИмяПоля, ВидСравнения.Равно, ОпцииПоиска); - КонецЕсли; - - НайденныеСущности = Получить(Коннектор, Пул, ОбъектМодели, ПулСущностей, ПередаваемыеОпцииПоиска); - - Если НайденныеСущности.Количество() = 0 Тогда - Возврат Неопределено; - Иначе - Возврат НайденныеСущности[0]; - КонецЕсли; - -КонецФункции - -// Удаляет удаление сущности из базы данных. -// Сущность должна иметь заполненный идентификатор. -// -// Параметры: -// Коннектор - АбстрактныйКоннектор - Коннектор занятого соединения. -// ОбъектМодели - ОбъектМодели - Объект, содержащий описание класса-сущности и настроек таблицы БД. -// ПулСущностей - СинхронизированнаяКарта - Ранее прочитанные экземпляры сущностей. -// Сущность - Произвольный - Удаляемая сущность -// -Процедура Удалить(Коннектор, ОбъектМодели, ПулСущностей, Сущность) Экспорт - Коннектор.Удалить(ОбъектМодели, Сущность); - // Пул сущностей разложен по идентификаторам: удаление по самой сущности не нашло бы ничего. - ПулСущностей.Удалить(ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность)); -КонецПроцедуры - // Посылает коннектору запрос на начало транзакции. // // Параметры: @@ -635,32 +628,27 @@ Коннектор.ОтменитьТранзакцию(); КонецПроцедуры -// <Описание процедуры> +// Проверяет, что тип класса размечен как сущность. // // Параметры: // ТипКласса - Тип - Тип, в котором проверяется наличие необходимых аннотаций. // Процедура ПроверитьЧтоКлассЯвляетсяСущностью(ТипКласса) - + РефлекторОбъекта = Новый РефлекторОбъекта(ТипКласса); ТаблицаМетодов = РефлекторОбъекта.ПолучитьТаблицуМетодов("Сущность", Ложь); Ожидаем.Что(ТаблицаМетодов, СтрШаблон("Класс %1 не имеет аннотации &Сущность", ТипКласса)).ИмеетДлину(1); - + ТаблицаСвойств = РефлекторОбъекта.ПолучитьТаблицуСвойств("Идентификатор"); Ожидаем.Что(ТаблицаСвойств, СтрШаблон("Класс %1 не имеет поля с аннотацией &Идентификатор", ТипКласса)).ИмеетДлину(1); - -КонецПроцедуры -Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) - // TODO: проверка должна быть в момент получения репозитория - Ожидаем.Что(ОбъектМодели, "Тип сущности не зарегистрирован в модели данных").Не_().Равно(Неопределено); КонецПроцедуры Процедура ПроверитьНеобходимостьЗаполненияИдентификатора(ОбъектМодели, Сущность) Если ОбъектМодели.Идентификатор().ГенерируемоеЗначение Тогда Возврат; КонецЕсли; - + ЗначениеИдентификатора = ОбъектМодели.ПолучитьЗначениеИдентификатора(Сущность); Ожидаем.Что( ЗначениеИдентификатора, СтрШаблон("Сущность с типом %1 должна иметь заполненный идентификатор", Тип(Сущность)) diff --git "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" "b/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" deleted file mode 100644 index 18c8ae56..00000000 --- "a/src/internal/\320\234\320\276\320\264\321\203\320\273\320\270/\320\245\321\200\320\260\320\275\320\270\320\273\320\270\321\211\320\260\320\241\321\203\321\211\320\275\320\276\321\201\321\202\320\265\320\271.os" +++ /dev/null @@ -1,129 +0,0 @@ -// Пул хранилищ сущностей: соответствие типа сущности хранилищу, общее на процесс. -// -// Ключ учитывает еще тип коннектора и строку соединения, поэтому хранилища разных баз не -// смешиваются. Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, -// притом из разных потоков, поэтому попадание в кэш читается под блокировкой чтения: -// сериализуется только создание. -// -#Использовать "../concurrent" - -// Соответствие - Ключ хранилища и структура с хранилищем и признаками его базы. -Перем Хранилища; -// БлокировкаЧтенияЗаписи - Защищает пул: он общий на все потоки. -Перем Блокировка; - -// Возвращает хранилище сущностей, создавая его при первом обращении. -// -// Параметры: -// ОбъектМодели - ОбъектМодели - Модель сущности, для которой нужно хранилище. -// Пул - ПулСоединенийСБД - Пул, которым хранилище выполняет операции. -// -// Возвращаемое значение: -// ХранилищеСущностей - Хранилище для переданной модели. -// -Функция Получить(ОбъектМодели, Пул) Экспорт - - КлючХранилища = ПолучитьКлючХранилища( - ОбъектМодели.ТипСущности(), - Пул.ТипКоннектора(), - Пул.СтрокаСоединения() - ); - - Блокировка.Чтение().Заблокировать(); - ДанныеХранилища = Хранилища.Получить(КлючХранилища); - Блокировка.Чтение().Разблокировать(); - - Если ДанныеХранилища <> Неопределено Тогда - Возврат ДанныеХранилища.Хранилище; - КонецЕсли; - - Блокировка.Запись().Заблокировать(); - - Попытка - - // Пока ждали запись, хранилище мог создать другой поток. - ДанныеХранилища = Хранилища.Получить(КлючХранилища); - Если ДанныеХранилища = Неопределено Тогда - ДанныеХранилища = Новый Структура("Хранилище, ТипКоннектора, СтрокаСоединения"); - ДанныеХранилища.Хранилище = Новый ХранилищеСущностей(ОбъектМодели, Пул); - ДанныеХранилища.ТипКоннектора = Пул.ТипКоннектора(); - ДанныеХранилища.СтрокаСоединения = Пул.СтрокаСоединения(); - Хранилища.Вставить(КлючХранилища, ДанныеХранилища); - КонецЕсли; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - - Возврат ДанныеХранилища.Хранилище; - -КонецФункции - -// Закрывает и убирает из пула хранилища указанной базы. -// -// Параметры: -// ТипКоннектора - Тип - Тип коннектора закрываемой базы. -// СтрокаСоединения - Строка - Строка соединения закрываемой базы. -// -Процедура Закрыть(ТипКоннектора, СтрокаСоединения) Экспорт - - Блокировка.Запись().Заблокировать(); - - Попытка - - ЗакрываемыеХранилища = Новый Массив; - Для Каждого КлючИЗначение Из Хранилища Цикл - ДанныеХранилища = КлючИЗначение.Значение; - - Если ДанныеХранилища.ТипКоннектора = ТипКоннектора - И ДанныеХранилища.СтрокаСоединения = СтрокаСоединения Тогда - - ДанныеХранилища.Хранилище.Закрыть(); - ЗакрываемыеХранилища.Добавить(КлючИЗначение.Ключ); - - КонецЕсли; - КонецЦикла; - - Для Каждого ЗакрываемоеХранилище Из ЗакрываемыеХранилища Цикл - Хранилища.Удалить(ЗакрываемоеХранилище); - КонецЦикла; - - Исключение - - Блокировка.Запись().Разблокировать(); - - ВызватьИсключение; - - КонецПопытки; - - Блокировка.Запись().Разблокировать(); - -КонецПроцедуры - -// Составляет ключ хранилища. -// -// Параметры: -// ТипСущности - Тип - Тип сущности. -// ТипКоннектора - Тип - Тип коннектора. -// СтрокаСоединения - Строка - Строка соединения. -// -// Возвращаемое значение: -// Строка - Ключ хранилища. -// -Функция ПолучитьКлючХранилища(ТипСущности, ТипКоннектора, СтрокаСоединения) - Возврат СтрШаблон( - "%1 - %2 - %3", - ТипСущности, - ТипКоннектора, - СтрокаСоединения - ); -КонецФункции - -Хранилища = Новый Соответствие(); -Блокировка = Новый БлокировкаЧтенияЗаписи(); 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 d6790aee..1a8aa907 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" @@ -1,4 +1,8 @@ +#Использовать "../internal" + #Использовать asserts +#Использовать atomic +#Использовать logos #Использовать reflector // Строка - Имя источника данных, по которому сущности выбирают источник в аннотации &Сущность. @@ -11,12 +15,59 @@ Перем пПараметрыКоннектора; // Число - Предел одновременно живущих соединений с БД; 0 - без предела. Перем пРазмерПула; +// ПулСоединенийСБД - Пул соединений источника. +Перем пПул; +// АтомарноеБулево - Закрывать источник по завершению потока, в котором он создан. Атомарное: +// сеттер зовут из одного потока, обработчик завершения читает в другом. +Перем пАвтоЗакрытие; +// АтомарноеБулево - Источник закрыт: пул закрыт и соединений больше не выдает. Атомарное: +// закрывает один поток, читают все, кто работает через источник. +Перем пЗакрыт; +// Лог - Журнал источника: в него уходит ошибка закрытия по завершению потока, жаловаться о которой +// уже некому. +Перем Лог; + +// Вызывается движком по завершению потока, создавшего источник: при включенном автозакрытии +// закрывает источник. Объявлен до конструктора намеренно: имя обработчика движок разрешает +// в один проход, и подписка на метод, объявленный ниже по модулю, не компилируется. +// +// Вызов через "ДобавитьОбработчик" статический анализ не видит, отсюда подавление диагностики. +// +// BSLLS:UnusedLocalMethod-off +Процедура ПриЗавершенииПотока() + + Если НЕ пАвтоЗакрытие.Получить() Тогда + Возврат; + КонецЕсли; -// Описание источника данных: имя, тип коннектора, строка соединения, параметры коннектора -// и размер пула соединений одним объектом. + // Подписчиков у завершения потока столько, сколько источников и пулов его застали, и движок + // обходит их одним циклом, не перехватывая ошибки обработчиков. Выпусти этот обработчик + // исключение, и до подписавшихся позже очередь не дойдет: их источники не закроются, а пулы + // не получат назад соединения завершившегося потока. Ошибка уходит в лог: поток кончился, + // и сообщать о ней больше некому. + Попытка + Закрыть(); + Исключение + Лог.Ошибка( + "Не удалось закрыть источник данных ""%1"" по завершению создавшего его потока: %2", + пИмя, + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + КонецПопытки; + +КонецПроцедуры +// BSLLS:UnusedLocalMethod-on + +// Источник данных: имя, тип коннектора, строка соединения, параметры коннектора, размер пула +// и сам пул соединений одним объектом. // -// Источник соединений не открывает, только описывает их. Тип коннектора проверяется при -// создании: тип без интерфейса АбстрактныйКоннектор отвергается с ошибкой. +// Пул создается вместе с источником и общий для всех, кто работает через источник; соединения +// пул открывает при первом обращении. Закрывает пул сам источник: методом Закрыть() или по +// завершению потока, в котором источник создан, если автозакрытие не выключено, см. +// УстановитьАвтоЗакрытие. Закрытие терминально: закрытый источник соединений больше не выдает, +// поэтому любая операция через него завершается ошибкой. Тип коннектора и размер пула +// проверяются при создании: тип без интерфейса АбстрактныйКоннектор и размер, не являющийся +// неотрицательным числом, отвергаются с ошибкой. // // Параметры: // Имя - Строка - Имя источника данных. @@ -39,6 +90,8 @@ ПроверитьПоддержкуИнтерфейсаКоннектора(ТипКоннектора); + Лог = Логирование.ПолучитьЛог("oscript.lib.entity.datasource"); + пИмя = Имя; пТипКоннектора = ТипКоннектора; пСтрокаСоединения = СтрокаСоединения; @@ -54,6 +107,13 @@ пПараметрыКоннектора = Новый ФиксированныйМассив(ПараметрыКоннектора); КонецЕсли; + пПул = Новый ПулСоединенийСБД(пРазмерПула, пТипКоннектора, пСтрокаСоединения, пПараметрыКоннектора); + пАвтоЗакрытие = Новый АтомарноеБулево(Истина); + пЗакрыт = Новый АтомарноеБулево(Ложь); + + // Источник живет не дольше создавшего его потока: о завершении сообщит сам поток. + ДобавитьОбработчик ТекущийПоток().ПриЗавершении, ПриЗавершенииПотока; + КонецПроцедуры // Проверяет, что тип коннектора реализует интерфейс АбстрактныйКоннектор. @@ -120,3 +180,80 @@ Функция РазмерПула() Экспорт Возврат пРазмерПула; КонецФункции + +// Включает или выключает закрытие источника по завершению потока, в котором он создан. +// +// По умолчанию включено. Выключает тот, кто управляет временем жизни источника сам: контейнер +// приложения или код, создающий источник в одном потоке для работы в других. Тогда источник +// закрывается только явным Закрыть(). +// +// Параметры: +// Значение - Булево - Истина - закрывать по завершению потока; Ложь - только явным Закрыть(). +// +Процедура УстановитьАвтоЗакрытие(Знач Значение) Экспорт + пАвтоЗакрытие.Установить(Значение); +КонецПроцедуры + +// Признак закрытия источника по завершению потока, см. УстановитьАвтоЗакрытие. +// +// Возвращаемое значение: +// Булево - Истина, если источник закрывается по завершению создавшего его потока. +// +Функция АвтоЗакрытие() Экспорт + Возврат пАвтоЗакрытие.Получить(); +КонецФункции + +// Закрывает пул соединений источника: все его соединения закрываются. +// +// Закрытие терминально: после него обращение через источник завершается ошибкой. Повторный +// вызов ничего не делает. Если закрытие завершилось ошибкой, источник закрытым не считается, +// и закрытие можно повторить: иначе оставшиеся соединения некому было бы закрыть. +// +Процедура Закрыть() Экспорт + + Если пЗакрыт.Получить() Тогда + Возврат; + КонецЕсли; + + // Признак выставляется после того, как пул закрыт, а не до: закрытие может упасть, и до + // удачного повтора источник закрытым не считается. Выстави признак раньше - и закрывающий, + // пришедший вторым, ушел бы молча, решив, что дело сделано, пока соединения еще живы. + // + // Второй закрывающий поэтому может застать пул незакрытым и закрыть его сам: повторное + // закрытие пул переживает. Своей блокировки поперек вызова в пул источник не держит: пул + // потокобезопасен сам, а вложенное ожидание вело бы к взаимной блокировке. + пПул.Закрыть(); + пЗакрыт.Установить(Истина); + + // Закрытые соединения и коннекторы больше никому не нужны: освобождаем их сразу, не дожидаясь + // сборщика. + ВыполнитьСборкуМусора(); + +КонецПроцедуры + +// Источник закрыт, см. Закрыть. +// +// Возвращаемое значение: +// Булево - Истина после закрытия, явного или по завершению потока. +// +Функция Закрыт() Экспорт + Возврат пЗакрыт.Получить(); +КонецФункции + +// BSLLS:UsingServiceTag-off +// @internal +// BSLLS:UsingServiceTag-on +// Пул соединений источника. Для закрытого источника завершается ошибкой. +// +// Возвращаемое значение: +// ПулСоединенийСБД - Пул соединений. +// +Функция ПулСоединений() Экспорт + + Если пЗакрыт.Получить() Тогда + ВызватьИсключение СтрШаблон("Источник данных <%1> закрыт", пИмя); + КонецЕсли; + + Возврат пПул; + +КонецФункции 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 ba280fef..5c5d7598 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" @@ -1,120 +1,76 @@ #Использовать "../Аннотации" #Использовать "../internal" +#Использовать "../internal/concurrent" #Использовать asserts +#Использовать atomic #Использовать logos #Использовать reflector // МодельДанных - Хранит данные о типах полей, колонках, настроек таблиц для типов сущностей Перем МодельДанных; -// ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пула. +// ИсточникДанных - Источник данных, из которого создан менеджер: параметры соединения и пул. Перем Источник; -// ПулСоединенийСБД - Выдает потокам исполнения отдельные коннекторы: через него идут все обращения к БД. -Перем ПулСоединений; +// Соответствие - Тип сущности: хранилище этого типа. Хранилища принадлежат менеджеру, потому что +// работают с его моделью данных: иначе хранилище с моделью закрытого менеджера досталось бы +// следующему. +Перем Хранилища; + +// БлокировкаЧтенияЗаписи - Защищает набор хранилищ: менеджером пользуются из разных потоков. +// Обращения идут на каждую прочитанную сущность и на каждое разыменование ссылки, поэтому +// попадание в набор читается под блокировкой чтения: сериализуется только создание. +Перем БлокировкаХранилищ; + +// АтомарноеБулево - Менеджер закрыт. Признак читают все методы менеджера, в том числе из других +// потоков и без блокировки набора хранилищ, поэтому атомарный. Выставляется под блокировкой записи: +// так хранилище, созданное после начала закрытия, в набор уже не попадет. +Перем Закрыт; // Лог - Журнал библиотеки. Перем Лог; // Конструктор объекта МенеджерСущностей. // -// Менеджер создается из источника данных: из него берутся тип коннектора, строка соединения, -// параметры коннектора и размер пула. Создание по параметрам соединения устарело и отмечается -// предупреждением в логе, как и параметры, переданные вместе с источником. +// Менеджер создается из источника данных и берет соединения из его пула: все менеджеры одного +// источника делят соединения и транзакции потоков. Параметры соединения задает источник, а его +// временем жизни управляет тот, кто источник создал. // // Параметры: // ИсточникДанных - ИсточникДанных - Источник данных, из которого создается менеджер. -// - Тип - Устарел. Тип класса, реализующего интерфейс Коннектор. -// УстарелСтрокаСоединения - Строка - Устарел. Строка соединения к БД, задается в источнике данных. -// УстарелПараметрыКоннектора - Массив - Устарел. Параметры коннектора, задаются в источнике данных. -// УстарелРазмерПулаСоединений - Число - Устарел. Размер пула соединений, задается в источнике данных. // // Пример: // Источник = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), СтрокаСоединения); // МенеджерСущностей = Новый МенеджерСущностей(Источник); // -Процедура ПриСозданииОбъекта( - Знач ИсточникДанных, - Знач УстарелСтрокаСоединения = "", - Знач УстарелПараметрыКоннектора = Неопределено, - Знач УстарелРазмерПулаСоединений = 10 -) +Процедура ПриСозданииОбъекта(Знач ИсточникДанных) Лог = Логирование.ПолучитьЛог("oscript.lib.entity.manager"); - Если ТипЗнч(ИсточникДанных) = Тип("ИсточникДанных") Тогда - Источник = ИсточникДанных; - ПредупредитьОЛишнихПараметрах( - УстарелСтрокаСоединения, - УстарелПараметрыКоннектора, - УстарелРазмерПулаСоединений - ); - Иначе - Лог.Предупреждение( - "Создание МенеджерСущностей по параметрам соединения устарело: создавайте менеджер из ИсточникДанных" - ); - Источник = Новый ИсточникДанных( - "", - ИсточникДанных, - УстарелСтрокаСоединения, - УстарелПараметрыКоннектора, - УстарелРазмерПулаСоединений - ); - КонецЕсли; + Ожидаем.Что( + ТипЗнч(ИсточникДанных) = Тип("ИсточникДанных"), + "МенеджерСущностей создается из ИсточникДанных, а не из параметров соединения" + ).ЭтоИстина(); + + Источник = ИсточникДанных; Лог.Отладка("Инициализация менеджера сущностей с коннектором %1", Источник.ТипКоннектора()); МодельДанных = Новый МодельДанных; - - // Соединений пул заранее не открывает, поэтому его можно завести сразу: коннектор доступен - // до Инициализировать(), как и раньше. Параметры коннектора берутся зафиксированными из источника. - ПулСоединений = Новый ПулСоединенийСБД( - Источник.РазмерПула(), - Источник.ТипКоннектора(), - Источник.СтрокаСоединения(), - Источник.ПараметрыКоннектора() - ); + Хранилища = Новый Соответствие(); + БлокировкаХранилищ = Новый БлокировкаЧтенияЗаписи(); + Закрыт = Новый АтомарноеБулево(Ложь); КонецПроцедуры -// Предупреждает, что вместе с источником данных переданы устаревшие параметры конструктора. -// -// Параметры: -// СтрокаСоединения - Строка - Второй параметр конструктора. -// ПараметрыКоннектора - Массив, Неопределено - Третий параметр конструктора. -// РазмерПулаСоединений - Число - Четвертый параметр конструктора. -// -Процедура ПредупредитьОЛишнихПараметрах(Знач СтрокаСоединения, Знач ПараметрыКоннектора, Знач РазмерПулаСоединений) - - ЕстьЛишние = ЗначениеЗаполнено(СтрокаСоединения) - ИЛИ ПараметрыКоннектора <> Неопределено - ИЛИ РазмерПулаСоединений <> РазмерПулаПоУмолчанию(); - - Если ЕстьЛишние Тогда - Лог.Предупреждение( - "При создании МенеджерСущностей из ИсточникДанных остальные параметры конструктора не используются: " - + "строка соединения, параметры коннектора и размер пула берутся из источника" - ); - КонецЕсли; - -КонецПроцедуры - -// Размер пула, подразумеваемый конструктором, когда он не задан. -// -// Возвращаемое значение: -// Число - Значение по умолчанию параметра УстарелРазмерПулаСоединений. -// -Функция РазмерПулаПоУмолчанию() - Возврат 10; // BSLLS:MagicNumber-off -КонецФункции - // Регистрирует переданный тип класса-сценария в модели данных. // // Параметры: // ТипСущности - Тип - Тип для добавления в модель // Процедура ДобавитьКлассВМодель(ТипСущности) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); ПроверитьЧтоКлассЯвляетсяСущностью(ТипСущности); МодельДанных.СоздатьОбъектМодели(ТипСущности); @@ -133,16 +89,19 @@ // Процедура Инициализировать() Экспорт - Соединение = ПулСоединений.Занять(); + ПроверитьЧтоМенеджерНеЗакрыт(); + + Пул = Источник.ПулСоединений(); + Соединение = Пул.Занять(); Попытка СоздатьТаблицы(Соединение.ПолучитьКоннектор()); Исключение - ПулСоединений.Освободить(); + Пул.Освободить(); ВызватьИсключение; КонецПопытки; - ПулСоединений.Освободить(); + Пул.Освободить(); КонецПроцедуры @@ -184,7 +143,7 @@ Функция СоздатьЭлемент(ТипСущности) Экспорт ОбъектМодели = МодельДанных.Получить(ТипСущности); - Возврат АктивнаяЗапись.СоздатьИзМенеджера(ОбъектМодели, ЭтотОбъект); + Возврат АктивнаяЗапись.Создать(ОбъектМодели, ПолучитьХранилищеСущностей(ТипСущности)); КонецФункции // Сохраняет сущность в БД. @@ -195,11 +154,13 @@ // Процедура Сохранить(Сущность) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.СохранитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Сохранить(ЭтотОбъект, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры @@ -220,13 +181,15 @@ // Функция Получить(ТипСущности, Знач ОпцииПоиска = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); Если ОпцииПоиска = Неопределено Тогда ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.Получить(ЭтотОбъект, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -248,13 +211,15 @@ // Функция ПолучитьОдно(ТипСущности, Знач ОпцииПоиска = Неопределено) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); Если ОпцииПоиска = Неопределено Тогда ОпцииПоиска = Новый ОпцииПоиска; КонецЕсли; - Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); + Возврат РаботаСКоннекторами.ПолучитьОдно(ЭтотОбъект, ОбъектМодели, ПулСущностей, ОпцииПоиска); КонецФункции @@ -267,29 +232,46 @@ // Процедура Удалить(Сущность) Экспорт + ПроверитьЧтоМенеджерНеЗакрыт(); + ТипСущности = АктивнаяЗапись.ТипСущности(Сущность); ОбъектМодели = МодельДанных.Получить(ТипСущности); ПулСущностей = ПолучитьПулСущностей(ТипСущности); - РаботаСКоннекторами.УдалитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + РаботаСКоннекторами.Удалить(ЭтотОбъект, ОбъектМодели, ПулСущностей, Сущность); КонецПроцедуры -// Выполняет очистку полную данных библиотеки. -// Дополнительно посылает всем используемым коннекторам запросы на закрытие соединения. +// Закрывает хранилища менеджера: после Закрыть() менеджер к работе непригоден. Закрытие +// терминально - у закрытого менеджера работают только геттеры и повторное закрытие, любой +// другой метод завершается ошибкой. // Процедура Закрыть() Экспорт - ХранилищаСущностей.Закрыть(Источник.ТипКоннектора(), Источник.СтрокаСоединения()); + БлокировкаХранилищ.Запись().Заблокировать(); - Если ПулСоединений <> Неопределено Тогда - ПулСоединений.Закрыть(); - КонецЕсли; + Попытка + + Для Каждого КлючИЗначение Из Хранилища Цикл + КлючИЗначение.Значение.Закрыть(); + КонецЦикла; + Хранилища.Очистить(); + + // Признак выставляется после того, как хранилища закрыты, а не до: споткнись закрытие + // на середине, менеджер закрытым не считается и закрытие можно повторить. Блокировка + // записи при этом не отпускалась, поэтому хранилище, запрошенное во время закрытия, + // увидит уже выставленный признак и создано не будет. + Закрыт.Установить(Истина); + + Исключение + + БлокировкаХранилищ.Запись().Разблокировать(); + + ВызватьИсключение; - МодельДанных.Очистить(); + КонецПопытки; - // Для освобожения ссылок на все коннекторы и соединения с СУБД - ВыполнитьСборкуМусора(); + БлокировкаХранилищ.Запись().Разблокировать(); КонецПроцедуры @@ -299,19 +281,22 @@ // попадают в эту транзакцию, а завершить ее другое фоновое задание не может. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулСоединений); + ПроверитьЧтоМенеджерНеЗакрыт(); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(ПулСоединений); + ПроверитьЧтоМенеджерНеЗакрыт(); + РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения и возвращает его соединение в пул. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(ПулСоединений); + ПроверитьЧтоМенеджерНеЗакрыт(); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -332,7 +317,8 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + ПроверитьЧтоМенеджерНеЗакрыт(); + РаботаСКоннекторами.ВыполнитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -354,28 +340,28 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + ПроверитьЧтоМенеджерНеЗакрыт(); + Возврат РаботаСКоннекторами.ВычислитьСКоннектором(Источник, Алгоритм, Контекст, Объект); КонецФункции -// BSLLS:UsingServiceTag-off -// @internal -// BSLLS:UsingServiceTag-on -// Для служебного пользования. +// Источник данных, из которого создан менеджер. // -// Возвращает пул соединений менеджера. -// -// Хранилища сущностей работают тем же пулом, что и менеджер: предел одновременно живущих -// соединений должен быть общим, а транзакция потока - видной из обоих. +// Все обращения менеджера к БД идут через соединения этого источника. // // Возвращаемое значение: -// ПулСоединенийСБД - Пул соединений менеджера. +// ИсточникДанных - Источник данных менеджера. // -Функция ПолучитьПулСоединений() Экспорт - Возврат ПулСоединений; +Функция ИсточникДанных() Экспорт + Возврат Источник; КонецФункции // Получает ХранилищеСущностей, привязанное к переданному типу сущности. // +// Хранилище создается при первом обращении и живет до закрытия менеджера: на один тип сущности +// менеджер всегда выдает одно и то же хранилище. Обращение к закрытому менеджеру завершается +// ошибкой, как и обращение за типом, не зарегистрированным в модели: такой тип можно +// зарегистрировать и получить хранилище снова. +// // Параметры: // ТипСущности - Тип - Тип сущности, зарегистрированный в Модели // @@ -383,11 +369,66 @@ // ХранилищеСущностей - Хранилище сущностей, привязанное к переданному типу сущности. // Функция ПолучитьХранилищеСущностей(ТипСущности) Экспорт - ОбъектМодели = МодельДанных.Получить(ТипСущности); - ХранилищеСущностей = ХранилищаСущностей.Получить(ОбъектМодели, ПулСоединений); - Возврат ХранилищеСущностей; + + БлокировкаХранилищ.Чтение().Заблокировать(); + Хранилище = Хранилища.Получить(ТипСущности); + БлокировкаХранилищ.Чтение().Разблокировать(); + + Если Хранилище <> Неопределено Тогда + Возврат Хранилище; + КонецЕсли; + + БлокировкаХранилищ.Запись().Заблокировать(); + + Попытка + + ПроверитьЧтоМенеджерНеЗакрыт(); + + // Пока ждали запись, хранилище мог создать другой поток. + Хранилище = Хранилища.Получить(ТипСущности); + Если Хранилище = Неопределено Тогда + ОбъектМодели = МодельДанных.Получить(ТипСущности); + ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели); + Хранилище = Новый ХранилищеСущностей(ОбъектМодели, ЭтотОбъект); + Хранилища.Вставить(ТипСущности, Хранилище); + КонецЕсли; + + Исключение + + БлокировкаХранилищ.Запись().Разблокировать(); + + ВызватьИсключение; + + КонецПопытки; + + БлокировкаХранилищ.Запись().Разблокировать(); + + Возврат Хранилище; + КонецФункции +// Проверяет, что менеджер не закрыт. +// +// Закрытие терминально: работать закрытым менеджером нельзя, и каждый его метод, кроме геттеров +// и повторного закрытия, начинается с этой проверки. +// +Процедура ПроверитьЧтоМенеджерНеЗакрыт() + Ожидаем.Что(Закрыт.Получить(), "Менеджер сущностей закрыт").ЭтоЛожь(); +КонецПроцедуры + +// Проверяет, что тип сущности зарегистрирован в модели данных менеджера. +// +// Вызывается до вставки хранилища в кэш: хранилище без модели нерабочее, и закэшируй его +// менеджер - регистрация класса следом уже ничего бы не исправила. +// +// Параметры: +// ОбъектМодели - ОбъектМодели - Модель типа сущности. +// Неопределено - Тип в модели не зарегистрирован. +// +Процедура ПроверитьЧтоТипСущностиЗарегистрированВМодели(ОбъектМодели) + Ожидаем.Что(ОбъектМодели, "Тип сущности не зарегистрирован в модели данных").Не_().Равно(Неопределено); +КонецПроцедуры + // @internal // // Для служебного пользования. diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\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\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" index 289d71b7..538f3bbc 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\276\320\264\320\265\320\273\321\214\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\234\320\276\320\264\320\265\320\273\321\214\320\224\320\260\320\275\320\275\321\213\321\205.os" @@ -23,7 +23,3 @@ Возврат ОбъектМодели; КонецФункции - -Процедура Очистить() Экспорт - ХранилищеМодели.Очистить(); -КонецПроцедуры diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\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/src/\320\232\320\273\320\260\321\201\321\201\321\213/\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 cea39a8d..6ed8e5ee 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\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/src/\320\232\320\273\320\260\321\201\321\201\321\213/\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" @@ -9,22 +9,23 @@ // Карта разделяется всеми потоками исполнения, поэтому синхронизированная. Перем ПулСущностей; -// ПулСоединенийСБД - Пул, из которого берется коннектор на каждую операцию. Общий с менеджером сущностей. -Перем ПулСоединений; +// МенеджерСущностей - Менеджер, которому принадлежит хранилище: хранилище подставляет в операции +// свой тип сущности и делегирует их ему. +Перем Менеджер; -// Создает хранилище, привязанное к типу сущности и работающее переданным пулом. +// Создает хранилище, привязанное к типу сущности. // -// Создается пулом хранилищ сущностей, напрямую конструировать не следует. +// Создается менеджером сущностей, напрямую конструировать не следует. // // Параметры: // ПОбъектМодели - ОбъектМодели - Модель сущности, к типу которой привязано хранилище. -// ППулСоединений - ПулСоединенийСБД - Пул, которым выполняются операции. +// ПМенеджер - МенеджерСущностей - Менеджер, создавший это хранилище. // -Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ППулСоединений) +Процедура ПриСозданииОбъекта(Знач ПОбъектМодели, Знач ПМенеджер) ОбъектМодели = ПОбъектМодели; ПулСущностей = Новый СинхронизированнаяКарта(Новый КартаСоответствие()); - ПулСоединений = ППулСоединений; + Менеджер = ПМенеджер; КонецПроцедуры @@ -35,7 +36,7 @@ // с дополнительными методами паттерна Active Record. // Функция СоздатьЭлемент() Экспорт - Возврат АктивнаяЗапись.СоздатьИзХранилища(ОбъектМодели, ЭтотОбъект); + Возврат АктивнаяЗапись.Создать(ОбъектМодели, ЭтотОбъект); КонецФункции // Сохраняет сущность в БД. @@ -47,7 +48,7 @@ // Сущность - Произвольный - Объект (экземпляр класса, зарегистрированного в модели) для сохранения в БД. // Процедура Сохранить(Сущность) Экспорт - РаботаСКоннекторами.СохранитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + Менеджер.Сохранить(Сущность); КонецПроцедуры // Осуществляет поиск сущностей типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -67,13 +68,7 @@ // экземпляры класса с типом, привязанным к ХранилищуСущностей, с заполненными значениями полей. // Функция Получить(Знач ОпцииПоиска = Неопределено) Экспорт - - Если ОпцииПоиска = Неопределено Тогда - ОпцииПоиска = Новый ОпцииПоиска(); - КонецЕсли; - - Возврат РаботаСКоннекторами.ПолучитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); - + Возврат Менеджер.Получить(ОбъектМодели.ТипСущности(), ОпцииПоиска); КонецФункции // Осуществляет поиск сущности типа, привязанного к ХранилищуСущностей, по идентификатору. @@ -94,13 +89,7 @@ // привязанным к ХранилищуСущностей, с заполненными значениями полей. Иначе возвращается "Неопределено". // Функция ПолучитьОдно(Знач ОпцииПоиска = Неопределено) Экспорт - - Если ОпцииПоиска = Неопределено Тогда - ОпцииПоиска = Новый ОпцииПоиска(); - КонецЕсли; - - Возврат РаботаСКоннекторами.ПолучитьОдноЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, ОпцииПоиска); - + Возврат Менеджер.ПолучитьОдно(ОбъектМодели.ТипСущности(), ОпцииПоиска); КонецФункции // Выполняет удаление сущности из базы данных. @@ -113,12 +102,12 @@ // Сущность - Произвольный - Удаляемая сущность // Процедура Удалить(Сущность) Экспорт - РаботаСКоннекторами.УдалитьЧерезПул(ПулСоединений, ОбъектМодели, ПулСущностей, Сущность); + Менеджер.Удалить(Сущность); КонецПроцедуры // Выполняет очистку текущего Хранилища сущностей. // -// Коннекторов хранилище не держит: они живут в пуле, и закрывает их менеджер сущностей. +// Очищает ранее прочитанные экземпляры: следующая операция читает сущность из БД заново. // Процедура Закрыть() Экспорт ПулСущностей.Очистить(); @@ -126,24 +115,23 @@ // Начинает транзакцию для текущего потока исполнения. // -// Транзакция принадлежит потоку, а не хранилищу: она открывается на коннекторе из пула и -// распространяется на все операции этого потока, в том числе через другие хранилища и через -// сам менеджер сущностей. +// Транзакция принадлежит потоку исполнения: в нее попадают все операции этого потока, а не только +// начатые через это хранилище. // Процедура НачатьТранзакцию() Экспорт - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулСоединений); + Менеджер.НачатьТранзакцию(); КонецПроцедуры // Фиксирует транзакцию текущего потока исполнения. // Процедура ЗафиксироватьТранзакцию() Экспорт - РаботаСКоннекторами.ЗафиксироватьТранзакциюПотокаИсполнения(ПулСоединений); + Менеджер.ЗафиксироватьТранзакцию(); КонецПроцедуры // Отменяет транзакцию текущего потока исполнения. // Процедура ОтменитьТранзакцию() Экспорт - РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(ПулСоединений); + Менеджер.ОтменитьТранзакцию(); КонецПроцедуры // Выполняет алгоритм коннектором из пула. @@ -164,7 +152,7 @@ // ); // Процедура ВыполнитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - РаботаСКоннекторами.ВыполнитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + Менеджер.ВыполнитьСКоннектором(Алгоритм, Контекст, Объект); КонецПроцедуры // Вычисляет значение алгоритмом, выполненным коннектором из пула. @@ -186,7 +174,7 @@ // Новый Структура("Запрос", "SELECT * FROM Авторы")); // Функция ВычислитьСКоннектором(Знач Алгоритм, Знач Контекст = Неопределено, Объект = Неопределено) Экспорт - Возврат РаботаСКоннекторами.ВычислитьСКоннекторомЧерезПул(ПулСоединений, Алгоритм, Контекст, Объект); + Возврат Менеджер.ВычислитьСКоннектором(Алгоритм, Контекст, Объект); КонецФункции // Возвращает объект модели, связанный с текущим хранилищем сущностей. 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 1f4e03dc..163aeffb 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" @@ -3,42 +3,37 @@ // Пул конструирует коннекторы сам, поэтому настроить экземпляр снаружи нельзя - настройки // приходят тем же путем, что и всегда, через параметры коннектора пула. // -// Параметры коннектора: -// [0] - Число - Пауза внутри открытия в миллисекундах. Изображает медленное подключение: -// рукопожатие, аутентификацию, недоступный хост. -// [1] - Булево - Истина, если открытие должно завершиться ошибкой. -// [2] - Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. +// Единственный параметр коннектора - НастройкиТестовогоКоннектора, см. описание того модуля. // -// BSLLS:MagicNumber-off // Булево - Состояние соединения. Перем Открыт; -// Булево - Признак того, что завершение транзакции должно падать. -Перем ПадатьПриЗавершенииТранзакции; +// НастройкиТестовогоКоннектора - Настройки, с которыми соединение открыто. +Перем Настройки; Процедура ПриСозданииОбъекта() Открыт = Ложь; - ПадатьПриЗавершенииТранзакции = Ложь; + Настройки = Неопределено; КонецПроцедуры -// Открывает соединение так, как задано параметрами коннектора. +// Открывает соединение так, как задано настройками. // // Параметры: // СтрокаСоединения - Строка - Не используется. -// ПараметрыКоннектора - Массив - Пауза и признак ошибки, см. описание модуля. +// ПараметрыКоннектора - Массив - Единственным элементом идут НастройкиТестовогоКоннектора. // Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт // BSLLS:UnusedParameters-off - Если ПараметрыКоннектора.Количество() > 0 И ПараметрыКоннектора[0] > 0 Тогда - Приостановить(ПараметрыКоннектора[0]); + Настройки = ПараметрыКоннектора[0]; + + Если Настройки.ПаузаОткрытия > 0 Тогда + Приостановить(Настройки.ПаузаОткрытия); КонецЕсли; - Если ПараметрыКоннектора.Количество() > 1 И ПараметрыКоннектора[1] Тогда + Если Настройки.ПадатьПриОткрытии Тогда ВызватьИсключение "Открытие соединения не удалось"; КонецЕсли; - ПадатьПриЗавершенииТранзакции = ПараметрыКоннектора.Количество() > 2 И ПараметрыКоннектора[2]; - Открыт = Истина; КонецПроцедуры @@ -64,15 +59,31 @@ // Общее поведение фиксации и отмены. // Процедура ЗавершитьТранзакцию() - Если ПадатьПриЗавершенииТранзакции Тогда + Если Настройки.ПадатьПриЗавершенииТранзакции Тогда ВызватьИсключение "Завершение транзакции не удалось"; КонецЕсли; КонецПроцедуры -// Закрывает соединение. +// Закрывает соединение, падая, если так задано параметрами. // Процедура Закрыть() Экспорт + + Если Настройки.НаблюдательЗакрытия <> Неопределено Тогда + Настройки.НаблюдательЗакрытия.ЗаписатьСостояниеИсточника(); + КонецЕсли; + + Если Настройки.ПланЗакрытий <> Неопределено Тогда + ПадаемСейчас = Настройки.ПланЗакрытий.ЗакрытиеПадает(); + Иначе + ПадаемСейчас = Настройки.ПадатьПриЗакрытии; + КонецЕсли; + + Если ПадаемСейчас Тогда + ВызватьИсключение "Закрытие соединения не удалось"; + КонецЕсли; + Открыт = Ложь; + КонецПроцедуры // Возвращает состояние соединения. @@ -83,3 +94,32 @@ Функция Открыт() Экспорт Возврат Открыт; КонецФункции + +// Операции над данными коннектор не поддерживает: он изображает только соединение. Методы +// объявлены, чтобы коннектор отвечал интерфейсу АбстрактныйКоннектор, который проверяет +// ИсточникДанных. Поиск отвечает пустым результатом: функции нужен возврат, а данных +// у коннектора нет. +// +// BSLLS:UnusedParameters-off + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Новый Массив; +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + ВызватьИсключение "Не поддерживается"; +КонецПроцедуры + +// BSLLS:UnusedParameters-on 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\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.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\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" new file mode 100644 index 00000000..5ad1eb6d --- /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\272\321\200\321\213\321\202\320\270\321\217\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -0,0 +1,48 @@ +// Запоминает, считался ли источник закрытым в тот момент, когда закрывались его соединения. +// +// Источник закрывает пул, а пул - каждое свое соединение, поэтому коннектор и есть та точка, +// из которой видно середину закрытия. Наблюдателя коннектор получает параметрами коннектора, +// как и все остальные настройки КоннекторУправляемоеОткрытие. +// + +// ИсточникДанных - Источник, за закрытием которого наблюдаем. +Перем Источник; +// Булево - Ответ источника на вопрос "закрыт?", заданный посреди закрытия. +Перем ИсточникСчиталсяЗакрытым; + +Процедура ПриСозданииОбъекта() + Источник = Неопределено; + ИсточникСчиталсяЗакрытым = Неопределено; +КонецПроцедуры + +// Задает источник, за которым наблюдаем. Вызывается после создания источника: раньше его нет, +// а параметры коннектора собираются до. +// +// Параметры: +// ПИсточник - ИсточникДанных - Источник. +// +Процедура УстановитьИсточник(ПИсточник) Экспорт + Источник = ПИсточник; +КонецПроцедуры + +// Спрашивает у источника, закрыт ли он. Вызывается коннектором из его Закрыть(). +// +Процедура ЗаписатьСостояниеИсточника() Экспорт + + Если Источник = Неопределено Тогда + Возврат; + КонецЕсли; + + ИсточникСчиталсяЗакрытым = Источник.Закрыт(); + +КонецПроцедуры + +// Состояние источника, замеченное посреди закрытия. +// +// Возвращаемое значение: +// Булево - Истина, если источник уже считал себя закрытым, когда его соединения еще закрывались. +// Неопределено - Соединения не закрывались, спросить было некогда. +// +Функция ИсточникСчиталсяЗакрытым() Экспорт + Возврат ИсточникСчиталсяЗакрытым; +КонецФункции diff --git "a/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" new file mode 100644 index 00000000..c208c37f --- /dev/null +++ "b/tests/fixtures/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270\320\242\320\265\321\201\321\202\320\276\320\262\320\276\320\263\320\276\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -0,0 +1,50 @@ +// Настройки КоннекторУправляемоеОткрытие: что коннектор делает на открытии, завершении транзакции +// и закрытии. +// +// Пул конструирует коннекторы сам, поэтому настроить экземпляр снаружи нельзя - настройки приходят +// тем же путем, что и всегда, через параметры коннектора. Единственным элементом массива параметров +// идет этот объект: позиционный список из полудюжины значений читать невозможно. +// +// Класс хранит только данные, поэтому поля открыты - как у ЭлементПорядка. +// +// BSLLS:ExportVariables-off + +// Число - Пауза внутри открытия в миллисекундах. Изображает медленное подключение: рукопожатие, +// аутентификацию, недоступный хост. +Перем ПаузаОткрытия Экспорт; +// Булево - Истина, если открытие должно завершиться ошибкой. +Перем ПадатьПриОткрытии Экспорт; +// Булево - Истина, если фиксация и отмена транзакции должны завершаться ошибкой. +Перем ПадатьПриЗавершенииТранзакции Экспорт; +// Булево - Истина, если закрытие должно завершиться ошибкой. +Перем ПадатьПриЗакрытии Экспорт; +// ПланЗакрытийКоннектора - Общий на пул план падений закрытия: с какого по счету закрытия начинать +// падать. Задан - решает он, а не ПадатьПриЗакрытии. +Перем ПланЗакрытий Экспорт; +// НаблюдательЗакрытияИсточника - Наблюдатель, которого коннектор спрашивает в начале закрытия. +Перем НаблюдательЗакрытия Экспорт; + +Процедура ПриСозданииОбъекта() + + ПаузаОткрытия = 0; + ПадатьПриОткрытии = Ложь; + ПадатьПриЗавершенииТранзакции = Ложь; + ПадатьПриЗакрытии = Ложь; + ПланЗакрытий = Неопределено; + НаблюдательЗакрытия = Неопределено; + +КонецПроцедуры + +// Собирает параметры коннектора для ИсточникДанных и ПулСоединенийСБД. +// +// Возвращаемое значение: +// Массив - Параметры коннектора с этими настройками единственным элементом. +// +Функция ПараметрыКоннектора() Экспорт + + Параметры = Новый Массив; + Параметры.Добавить(ЭтотОбъект); + + Возврат Параметры; + +КонецФункции diff --git "a/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" new file mode 100644 index 00000000..87cde721 --- /dev/null +++ "b/tests/fixtures/\320\237\320\273\320\260\320\275\320\227\320\260\320\272\321\200\321\213\321\202\320\270\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\260.os" @@ -0,0 +1,51 @@ +// План закрытий для КоннекторУправляемоеОткрытие: с какого по счету закрытия начинать падать. +// +// Один экземпляр разделяется всеми коннекторами пула: он приходит к ним параметрами коннектора, +// а параметры пул передает каждому создаваемому коннектору как есть. Так в одном пуле получаются +// соединения с разной судьбой закрытия, чего одним признаком "падать при закрытии" не добиться. +// + +// Число - Номер закрытия, начиная с которого закрытие завершается ошибкой. +Перем ПадатьНачинаяС; +// Число - Сколько закрытий подряд падает, начиная с этого номера. Ноль - падают все следующие. +Перем КоличествоПадений; +// Число - Сколько закрытий уже выполнено по этому плану. +Перем ВыполненоЗакрытий; + +// План закрытий. +// +// Параметры: +// ПНомерПервогоПадения - Число - Номер закрытия, с которого начинаются ошибки. Первое закрытие +// имеет номер 1. +// ПКоличествоПадений - Число - Сколько закрытий подряд падает. Ноль - все закрытия начиная +// с первого падения. Конечное число нужно там, где проверяется удачный повтор закрытия: +// пул повторяет закрытие того же соединения, и вечно падающий коннектор закрыть его не даст. +// +Процедура ПриСозданииОбъекта(Знач ПНомерПервогоПадения, Знач ПКоличествоПадений = 0) + + ПадатьНачинаяС = ПНомерПервогоПадения; + КоличествоПадений = ПКоличествоПадений; + ВыполненоЗакрытий = 0; + +КонецПроцедуры + +// Регистрирует очередное закрытие и отвечает, должно ли оно завершиться ошибкой. +// +// Возвращаемое значение: +// Булево - Истина, если это закрытие должно упасть. +// +Функция ЗакрытиеПадает() Экспорт + + ВыполненоЗакрытий = ВыполненоЗакрытий + 1; + + Если ВыполненоЗакрытий < ПадатьНачинаяС Тогда + Возврат Ложь; + КонецЕсли; + + Если КоличествоПадений = 0 Тогда + Возврат Истина; + КонецЕсли; + + Возврат ВыполненоЗакрытий < ПадатьНачинаяС + КоличествоПадений; + +КонецФункции 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 f6d30473..3e2b726b 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" @@ -1,3 +1,12 @@ +// Помощники, общие для тестовых наборов. +// +// Значения по умолчанию для переменных среды - localhost и postgres - повторяются и выглядят +// для анализатора захардкоженным адресом и дублем литерала, поэтому обе диагностики здесь сняты: +// в наборах, откуда эти помощники переехали, они были сняты по той же причине. +// +// BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off + Функция ПолучитьПеременнуюСредыИлиЗначение(ИмяПеременной, ЗначениеПоУмолчанию) Экспорт ПеременнаяСреды = ПолучитьПеременнуюСреды(ИмяПеременной); Если ПеременнаяСреды = Неопределено Тогда @@ -38,6 +47,70 @@ КонецФункции +// Подключает классы сущностей Автор и СущностьБезГенерируемогоИдентификатора. +// +// Повторное подключение безвредно: наборы зовут его перед каждым тестом. +// +Процедура ПодключитьКлассыСущностей() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "СущностьБезГенерируемогоИдентификатора.os"), + "СущностьБезГенерируемогоИдентификатора" + ); + ПодключитьСценарий(ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор"); + +КонецПроцедуры + +// Менеджер источника с моделью из Автора и того, на что Автор ссылается, и созданными таблицами. +// +// Параметры: +// Источник - ИсточникДанных - Источник. +// +// Возвращаемое значение: +// МенеджерСущностей - Менеджер с инициализированной моделью. +// +Функция МенеджерАвторов(Источник) Экспорт + + ПодключитьКлассыСущностей(); + + Менеджер = Новый МенеджерСущностей(Источник); + // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Возврат Менеджер; + +КонецФункции + +// Источник на СУБД с транзакциями: SQLite в памяти, если тесты SQLite включены, иначе PostgreSQL +// по переменным среды. +// +// Возвращаемое значение: +// ИсточникДанных - Источник. +// +Функция ИсточникСТранзакциями() Экспорт + + Если ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_SQLITE_TESTS", "true") = "true" Тогда + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "FullUri=file::memory:?cache=shared"); + КонецЕсли; + + Если ПолучитьПеременнуюСредыИлиЗначение("TESTRUNNER_RUN_POSTGRES_TESTS", "true") = "true" Тогда + СтрокаСоединения = СтрШаблон( + "Host=%1;Port=%2;Username=%3;Password=%4;Database=%5", + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_HOST", "localhost"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PORT", "5432"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_USERNAME", "postgres"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_PASSWORD", "postgres"), + ПолучитьПеременнуюСредыИлиЗначение("POSTGRES_DATABASE", "postgres") + ); + Возврат Новый ИсточникДанных("Основной", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + КонецЕсли; + + ВызватьИсключение "Нет доступного коннектора с транзакциями"; + +КонецФункции + // Удаляет таблицы в базе данных менеджера сущностей. // // Параметры: diff --git "a/tests/\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/tests/\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 9ecf5be3..d625b800 100644 --- "a/tests/\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/tests/\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" @@ -2,5 +2,6 @@ &Тест Процедура АбстрактныйКоннекторРеализуетИнтерфейсКоннектора() Экспорт - МенеджерСущностей = Новый МенеджерСущностей(Тип("АбстрактныйКоннектор")); // BSLLS:UnusedLocalVariable-off + Источник = Новый ИсточникДанных("Тесты", Тип("АбстрактныйКоннектор")); + Источник.Закрыть(); КонецПроцедуры diff --git "a/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" new file mode 100644 index 00000000..da4bc0a8 --- /dev/null +++ "b/tests/\320\222\320\275\321\203\321\202\321\200\320\265\320\275\320\275\320\270\320\271\320\232\320\276\320\275\321\202\321\200\320\260\320\272\321\202\320\230\321\201\321\202\320\276\321\207\320\275\320\270\320\272\320\260.os" @@ -0,0 +1,106 @@ +// Внутреннее API библиотеки работает через источник данных, а не через его пул соединений. +// +// Пул остается деталью источника: с ним напрямую работают только методы, занимающие и +// освобождающие соединения. Кэш хранилищ и операции над сущностями получают источник. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off + +#Использовать ".." +#Использовать "../src/internal" +#Использовать "utils" + +// Хранилища принадлежат менеджеру: он выдает одно и то же хранилище на тип и закрывает свои +// хранилища сам. +&Тест +Процедура МенеджерВыдаетОдноХранилищеНаТип() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище менеджера"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + Ожидаем.Что(Хранилище.Получить(), "Хранилище менеджера работает").ИмеетДлину(0); + Ожидаем.Что(Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")), "Повторное обращение дает то же хранилище") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Хранилище закрытого источника получить можно: к БД это обращение не идет. Ошибку дает первая +// же операция, когда хранилище просит у источника соединение. +&Тест +Процедура ОперацияХранилищаЗакрытогоИсточникаЗавершаетсяОшибкой() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище после закрытия"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + Источник.Закрыть(); + + ТекстОшибки = ""; + Попытка + Хранилище.Получить(); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Операция через закрытый источник завершается ошибкой").Содержит("закрыт"); + +КонецПроцедуры + +&Тест +Процедура АлгоритмВыполняетсяКоннекторомЧерезИсточник() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "алгоритм через источник"); + + ТипКоннектора = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> ТипЗнч(Коннектор)"); + + Ожидаем.Что(ТипКоннектора, "Алгоритм получил коннектор источника").Равно(Тип("КоннекторInMemory")); + + Источник.Закрыть(); + +КонецПроцедуры + +// Операция идет соединением источника, а метаданные и пул сущностей берутся у хранилища: оба +// принадлежат одному менеджеру, поэтому рассогласоваться не могут. +&Тест +Процедура СущностьСохраняетсяИЧитаетсяЧерезХранилище() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "операции через хранилище"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + + Автор = Новый Автор; + Автор.Имя = "Иван"; + Автор.ВтороеИмя = "Иванов"; + Хранилище.Сохранить(Автор); + + Ожидаем.Что(Хранилище.Получить(), "Сохраненный автор прочитан хранилищем").ИмеетДлину(1); + Ожидаем + .Что(Хранилище.ПолучитьОбъектМодели() = Менеджер.ПолучитьМодельДанных().Получить(Тип("Автор")), + "Хранилище работает с объектом модели своего менеджера") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Транзакции нужен коннектор СУБД: КоннекторInMemory их не поддерживает. +&Тест +Процедура ТранзакцияПотокаОткрываетсяЧерезИсточник() Экспорт + + Источник = ТестовыеУтилиты.ИсточникСТранзакциями(); + + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(Источник); + Первый = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> Коннектор"); + Второй = РаботаСКоннекторами.ВычислитьСКоннектором(Источник, "Коннектор -> Коннектор"); + РаботаСКоннекторами.ОтменитьТранзакциюПотокаИсполнения(Источник); + + Ожидаем.Что(Первый = Второй, "Операции потока попадают на коннектор транзакции").ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры diff --git "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" index 852fcf14..140fcafa 100644 --- "a/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" +++ "b/tests/\320\224\320\276\321\201\321\202\321\203\320\277\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\276\320\274.os" @@ -9,6 +9,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Хранилище авторов, полученное из менеджера. @@ -37,7 +39,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования доступа коннектором"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -67,6 +72,7 @@ Хранилище = Неопределено; МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест @@ -160,7 +166,7 @@ .ВыбрасываетИсключение(); Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -177,7 +183,7 @@ .ВыбрасываетИсключение(); Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -223,7 +229,7 @@ КонецПопытки; Ожидаем - .Что(МенеджерСущностей.ПолучитьПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") + .Что(МенеджерСущностей.ИсточникДанных().ПулСоединений().КоличествоЗанятых(), "Соединение возвращено в пул") .Равно(0); КонецПроцедуры @@ -261,37 +267,12 @@ КонецПроцедуры -// Параметры коннектора, переданные в конструктор менеджера, доходят до пула. -// -&Тест -Процедура ПараметрыКоннектораДоходятДоПула() Экспорт - - ПараметрыКоннектора = Новый Массив; - ПараметрыКоннектора.Добавить("ПроизвольныйПараметр"); - - Менеджер = Новый МенеджерСущностей(Тип("КоннекторInMemory"), "проверка параметров", ПараметрыКоннектора); - // Массив вызывающего кода менеджеру не принадлежит: правка после создания до пула не доходит. - ПараметрыКоннектора.Добавить("ЛишнийПараметр"); - - Попытка - ПараметрыПула = Менеджер.ПолучитьПулСоединений().ПараметрыКоннектора(); - Ожидаем.Что(ПараметрыПула, "Параметры дошли до пула").ИмеетДлину(1); - Ожидаем.Что(ПараметрыПула[0]).Равно("ПроизвольныйПараметр"); - Исключение - Менеджер.Закрыть(); - ВызватьИсключение; - КонецПопытки; - - Менеджер.Закрыть(); - -КонецПроцедуры - // Соединение знает, начата ли им транзакция. // &Тест Процедура СоединениеЗнаетОСвоейТранзакции() Экспорт - Пул = МенеджерСущностей.ПолучитьПулСоединений(); + Пул = МенеджерСущностей.ИсточникДанных().ПулСоединений(); МенеджерСущностей.НачатьТранзакцию(); diff --git "a/tests/\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/tests/\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 afbc7ecd..40d7bd02 100644 --- "a/tests/\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/tests/\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" @@ -7,7 +7,7 @@ #Использовать ".." &Тест -Процедура РазмерПулаПоУмолчаниюСовпадаетСМенеджером() Экспорт +Процедура ИсточникСоздаетсяСоЗначениямиПоУмолчанию() Экспорт Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory")); @@ -15,7 +15,7 @@ Ожидаем.Что(Источник.ТипКоннектора()).Равно(Тип("КоннекторInMemory")); Ожидаем.Что(Источник.СтрокаСоединения()).Равно(""); Ожидаем.Что(Источник.ПараметрыКоннектора()).ИмеетДлину(0); - Ожидаем.Что(Источник.РазмерПула(), "Размер пула по умолчанию тот же, что у конструктора менеджера").Равно(10); + Ожидаем.Что(Источник.РазмерПула(), "Размер пула по умолчанию").Равно(10); КонецПроцедуры @@ -34,6 +34,22 @@ КонецПроцедуры +// Пул создается вместе с источником, поэтому и размер пула проверяется при создании. +&Тест +Процедура НеверныйРазмерПулаОтвергаетсяПриСоздании() Экспорт + + ТекстОшибки = ""; + Попытка + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "", , -1); // BSLLS:UnusedLocalVariable-off + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Источник отвергает отрицательный размер пула") + .Содержит("Размер пула соединений должен быть неотрицательным числом"); + +КонецПроцедуры + &Тест Процедура РазмерПулаЗадаетсяПятымПараметром() Экспорт @@ -62,62 +78,73 @@ КонецПроцедуры +// Менеджер работает коннектором того типа, который задан источником: другого способа узнать, +// чем ходить в базу, у менеджера нет. &Тест Процедура МенеджерСоздаетсяИзИсточникаДанных() Экспорт - Параметры = Новый Массив; - Параметры.Добавить("параметр"); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", Параметры, 3); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник"); МенеджерСущностей = Новый МенеджерСущностей(Источник); - МенеджерСущностей.Инициализировать(); - Пул = МенеджерСущностей.ПолучитьПулСоединений(); - Ожидаем.Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); - Ожидаем.Что(Пул.СтрокаСоединения()).Равно("источник"); - Ожидаем.Что(Пул.ПараметрыКоннектора()).Равно(Источник.ПараметрыКоннектора()); - Ожидаем.Что(Пул.РазмерПула()).Равно(3); + Ожидаем + .Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)"), "Коннектор источника") + .Равно(Тип("КоннекторInMemory")); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры +// Массив вызывающего кода источнику не принадлежит: источник снимает с него копию, и правка +// после создания параметров источника не меняет. &Тест -Процедура ЛишниеПараметрыКонструктораПриИсточникеНеИспользуются() Экспорт +Процедура ПравкаМассиваПослеСозданияПараметровНеМеняет() Экспорт - ПараметрыИсточника = Новый Массив(); - ПараметрыИсточника.Добавить("из источника"); - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", ПараметрыИсточника, 3); + Параметры = Новый Массив; + Параметры.Добавить("ПроизвольныйПараметр"); - ЧужиеПараметры = Новый Массив(); - ЧужиеПараметры.Добавить("из конструктора"); - МенеджерСущностей = Новый МенеджерСущностей(Источник, "другая строка", ЧужиеПараметры, 7); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "", Параметры); - Пул = МенеджерСущностей.ПолучитьПулСоединений(); - Ожидаем.Что(Пул.СтрокаСоединения(), "Строка соединения взята из источника").Равно("источник"); - Ожидаем.Что(Пул.ПараметрыКоннектора(), "Параметры коннектора взяты из источника") - .Равно(Источник.ПараметрыКоннектора()); - Ожидаем.Что(Пул.РазмерПула(), "Размер пула взят из источника").Равно(3); + Параметры.Добавить("ЛишнийПараметр"); - МенеджерСущностей.Закрыть(); + Ожидаем.Что(Источник.ПараметрыКоннектора(), "Лишний параметр в источник не попал").ИмеетДлину(1); + Ожидаем.Что(Источник.ПараметрыКоннектора()[0]).Равно("ПроизвольныйПараметр"); + + Источник.Закрыть(); КонецПроцедуры +// Геттер автозакрытия показывает, закроется ли источник по завершению создавшего его потока: +// по этому признаку владелец источника узнает, управляет ли он временем жизни источника сам. &Тест -Процедура МенеджерСоздаетсяПоПолямИсточникаУстаревшимСпособом() Экспорт +Процедура АвтоЗакрытиеВключеноПоУмолчаниюИЧитаетсяПослеСеттера() Экспорт - Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник", , 0); + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие по завершению потока"); - МенеджерСущностей = Новый МенеджерСущностей( - Источник.ТипКоннектора(), - Источник.СтрокаСоединения(), - Источник.ПараметрыКоннектора(), - Источник.РазмерПула() - ); - МенеджерСущностей.Инициализировать(); + Ожидаем.Что(Источник.АвтоЗакрытие(), "Новый источник закрывается по завершению потока").ЭтоИстина(); - Ожидаем.Что(МенеджерСущностей.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + Источник.УстановитьАвтоЗакрытие(Ложь); - МенеджерСущностей.Закрыть(); + Ожидаем.Что(Источник.АвтоЗакрытие(), "После сеттера источник переживает поток").ЭтоЛожь(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Параметры соединения задает только источник: менеджер их не принимает и сам источник +// не создает. +&Тест +Процедура МенеджерБезИсточникаДанныхНеСоздается() Экспорт + + ТекстОшибки = ""; + Попытка + МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory")); // BSLLS:UnusedLocalVariable-off + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Менеджер отвергает параметры соединения вместо источника") + .Содержит("создается из ИсточникДанных"); КонецПроцедуры diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" index c8749b75..a47168c3 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" @@ -9,6 +9,8 @@ Перем Коннектор; Перем СтрокаСоединения; +// ИсточникДанных - Источник менеджера, созданного в теле теста: закрывается после теста. +Перем ИсточникТеста; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -24,6 +26,10 @@ &ПослеКаждого Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); + Если ИсточникТеста <> Неопределено Тогда + ИсточникТеста.Закрыть(); + ИсточникТеста = Неопределено; + КонецЕсли; КонецПроцедуры &Тест @@ -389,7 +395,8 @@ ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -413,7 +420,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -440,7 +448,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -465,7 +474,8 @@ // Дано ПодключитьСценарий("tests/fixtures/ПростойОбъект.os", "ПростойОбъект"); ТипКоннектора = "КоннекторInMemory"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора)); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора)); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -490,7 +500,8 @@ &Тест Процедура РаботаСИменованнымПолем() Экспорт // Дано - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory")); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory")); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьСИменованнымПолем")); МенеджерСущностей.Инициализировать(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" index a2f141ad..7a081569 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" @@ -13,6 +13,8 @@ // Объект модели, разделяемый главным потоком и фоновым заданием // в тесте "ЧтениеНеКонфликтуетСПараллельнойЗаписью". Перем ОбъектМоделиФоновойЗаписи; +// ИсточникДанных - Источник менеджера, созданного в теле теста: закрывается после теста. +Перем ИсточникТеста; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -29,6 +31,10 @@ &ПослеКаждого Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); + Если ИсточникТеста <> Неопределено Тогда + ИсточникТеста.Закрыть(); + ИсточникТеста = Неопределено; + КонецЕсли; УдалитьФайлы(СтрокаСоединения); КонецПроцедуры @@ -322,7 +328,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -348,7 +355,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -377,7 +385,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); @@ -404,7 +413,8 @@ КаталогБД = "./tests/jsondatabase"; ФС.ОбеспечитьПустойКаталог(КаталогБД); ТипКоннектора = "КоннекторJSON"; - МенеджерСущностей = Новый МенеджерСущностей(Тип(ТипКоннектора), КаталогБД); + ИсточникТеста = Новый ИсточникДанных("Тесты", Тип(ТипКоннектора), КаталогБД); + МенеджерСущностей = Новый МенеджерСущностей(ИсточникТеста); МенеджерСущностей.ДобавитьКлассВМодель(Тип("ПростойОбъект")); МенеджерСущностей.Инициализировать(); diff --git "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index 4b4afc67..a9cbb294 100644 --- "a/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/tests/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -6,6 +6,8 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник менеджера набора: создается в подготовке, закрывается после теста. +Перем Источник; Перем Коннектор; &ВключенЕсли("() -> ЗапускатьТестыPostgreSQL()") @@ -50,10 +52,13 @@ ИмяБД, Порт ); - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторPostgreSQL"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторPostgreSQL"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник не должен закрыться с потоком подготовки. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.Инициализировать(); - // Тесты коннектора работают собственным экземпляром: соединения из пула принадлежат пулу. + // Тесты коннектора работают с собственным экземпляром: соединения из пула принадлежат пулу. Коннектор = Новый КоннекторPostgreSQL; Коннектор.Открыть(СтрокаСоединения, Новый Массив); @@ -66,6 +71,7 @@ ТестовыеУтилиты.УдалитьТаблицыВБазеДанных(Коннектор); Коннектор.Закрыть(); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры &Тест 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 9fc47ff9..2efc042c 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" @@ -7,6 +7,8 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник менеджера набора: создается в подготовке, закрывается после теста. +Перем Источник; Перем Коннектор; &ВключенЕсли("() -> ЗапускатьТестыSQLite()") @@ -32,7 +34,10 @@ СтрокаСоединения = "Data Source=:memory:"; // СтрокаСоединения = "Data Source=test.db"; - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторSQLite"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник не должен закрыться с потоком подготовки. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); МенеджерСущностей.ДобавитьКлассВМодель(Тип("АвтоинкрементныйКлючБезКолонок")); МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); @@ -40,7 +45,7 @@ МенеджерСущностей.Инициализировать(); - // Тесты коннектора работают собственным экземпляром: соединения из пула принадлежат пулу. + // Тесты коннектора работают с собственным экземпляром: соединения из пула принадлежат пулу. Коннектор = Новый КоннекторSQLite; Коннектор.Открыть(СтрокаСоединения, Новый Массив); @@ -50,6 +55,7 @@ Процедура ПослеЗапускаТеста() Экспорт Коннектор.Закрыть(); МенеджерСущностей.Закрыть(); + Источник.Закрыть(); КонецПроцедуры &Тест diff --git "a/tests/\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/tests/\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 5ba7e731..62136a68 100644 --- "a/tests/\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/tests/\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" @@ -7,6 +7,8 @@ #Использовать "utils" Перем МенеджерСущностей; +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -39,7 +41,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования менеджера сущностей"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -85,6 +90,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест @@ -166,14 +172,38 @@ СохраняемыйАвтор.ВтороеИмя = "Иванов"; СохраняемыйАвтор.Сохранить(); - + Результат = ТестовыеУтилиты.ВыполнитьЗапрос(МенеджерСущностей, "SELECT * FROM Авторы"); Ожидаем.Что(Результат, "В таблице должен был сохраниться новый автор").ИмеетДлину(1); - + Ожидаем .Что(СохраняемыйАвтор.ВнутреннийИдентификатор, "Заполнился и сохранился новый идентификатор сохраняемого автора") .Равно(1); - + +КонецПроцедуры + +// Активная запись строится одним построителем независимо от того, кто ее создал: и элемент +// менеджера, и элемент хранилища работают через хранилище своего типа. +&Тест +Процедура ЭлементМенеджераРаботаетЧерезХранилищеСвоегоТипа() Экспорт + + ХранилищеАвторов = МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("Автор")); + + ЭлементМенеджера = МенеджерСущностей.СоздатьЭлемент(Тип("Автор")); + ЭлементХранилища = ХранилищеАвторов.СоздатьЭлемент(); + + Рефлектор = Новый Рефлектор(); + + Ожидаем.Что( + Рефлектор.ПолучитьСвойство(ЭлементХранилища, "_ХранилищеСущностей") = ХранилищеАвторов, + "Элемент хранилища работает через это хранилище" + ).ЭтоИстина(); + + Ожидаем.Что( + Рефлектор.ПолучитьСвойство(ЭлементМенеджера, "_ХранилищеСущностей") = ХранилищеАвторов, + "Элемент менеджера работает через то же хранилище" + ).ЭтоИстина(); + КонецПроцедуры &Тест diff --git "a/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" "b/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" index 1b686ae5..e3ea52a3 100644 --- "a/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" +++ "b/tests/\320\234\320\275\320\276\320\263\320\276\320\277\320\276\321\202\320\276\321\207\320\275\320\276\321\201\321\202\321\214\320\244\320\276\320\275\320\276\320\262\321\213\320\265\320\227\320\260\320\264\320\260\320\275\320\270\321\217.os" @@ -12,6 +12,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; @@ -33,7 +35,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования многопоточной работы"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектора, СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -60,6 +65,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Фоновое задание фиксирует свою транзакцию, а основной поток затем откатывает свою. diff --git "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" index 76f6090a..995625d4 100644 --- "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" +++ "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214InMemory.os" @@ -13,6 +13,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Строка - Строка соединения с базой в памяти. @@ -33,7 +35,10 @@ "СущностьБезГенерируемогоИдентификатора" ); - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторInMemory"), СтрокаСоединения); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторInMemory"), СтрокаСоединения); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); // Автор ссылается на СущностьБезГенерируемогоИдентификатора, поэтому в модели нужны обе. МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); @@ -47,6 +52,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Четыре задания одновременно пишут авторов. Ни одна строка не должна потеряться, и ни один diff --git "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" index 3be5e511..f1ca0c5e 100644 --- "a/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" +++ "b/tests/\320\237\320\260\321\200\320\260\320\273\320\273\320\265\320\273\321\214\320\275\320\260\321\217\320\227\320\260\320\277\320\270\321\201\321\214PostgreSQL.os" @@ -18,6 +18,8 @@ // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; &ВключенЕсли("() -> ЗапускатьТестыPostgreSQL()") &ТестовыйНабор @@ -36,7 +38,10 @@ &ПередКаждым Процедура ПередЗапускомТеста() Экспорт - МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторPostgreSQL"), СтрокаСоединенияPostgres()); + Источник = Новый ИсточникДанных("Тесты", Тип("КоннекторPostgreSQL"), СтрокаСоединенияPostgres()); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -60,6 +65,7 @@ Процедура ПослеЗапускаТеста() Экспорт МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры // Четыре задания одновременно пишут авторов. Ни одна строка не должна потеряться, и ни один diff --git "a/tests/\320\237\321\203\320\273\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\237\321\203\320\273\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..2dc96617 --- /dev/null +++ "b/tests/\320\237\321\203\320\273\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,481 @@ +// Пул соединений принадлежит источнику данных: менеджеры одного источника делят пул и +// транзакцию потока, а закрывает пул источник - явно или по завершению потока, в котором +// источник создан. +// +// BSLLS:MagicNumber-off +// BSLLS:DuplicateStringLiteral-off +// BSLLS:UsingHardcodeNetworkAddress-off + +#Использовать ".." +#Использовать "utils" + +// ИсточникДанных - Источник, созданный в другом потоке; поток теста проверяет его после +// завершения потока создания. Другой поток дает фоновое задание. +Перем ИсточникДругогоПотока; +// ИсточникДанных - Второй источник того же потока: он подписывается на завершение потока позже +// первого, поэтому по нему видно, дошла ли до него очередь обработчиков. +Перем ВторойИсточникДругогоПотока; + +&Тест +Процедура МенеджерыОдногоИсточникаДелятПул() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "общий пул"); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + + Ожидаем.Что(Первый.ИсточникДанных().ПулСоединений() = Второй.ИсточникДанных().ПулСоединений(), "Пул один на источник") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура МенеджерВозвращаетСвойИсточник() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "источник менеджера"); + Менеджер = Новый МенеджерСущностей(Источник); + + Ожидаем.Что(Менеджер.ИсточникДанных() = Источник, "Менеджер отдает источник, из которого создан").ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Транзакции нужен коннектор СУБД: КоннекторInMemory их не поддерживает. +&Тест +Процедура ТранзакцияПотокаОбщаяДляМенеджеровИсточника() Экспорт + + Источник = ТестовыеУтилиты.ИсточникСТранзакциями(); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + + Первый.НачатьТранзакцию(); + КоннекторПервого = Первый.ВычислитьСКоннектором("Коннектор -> Коннектор"); + КоннекторВторого = Второй.ВычислитьСКоннектором("Коннектор -> Коннектор"); + Первый.ОтменитьТранзакцию(); + + Ожидаем.Что(КоннекторПервого = КоннекторВторого, "Второй менеджер попадает в транзакцию потока") + .ЭтоИстина(); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ЗакрытиеМенеджераНеЗакрываетПулИсточника() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "живой пул"); + Первый = Новый МенеджерСущностей(Источник); + Второй = Новый МенеджерСущностей(Источник); + Первый.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Первый.Закрыть(); + + Ожидаем.Что(Источник.Закрыт(), "Закрытие менеджера источник не закрывает").ЭтоЛожь(); + Ожидаем.Что(Второй.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ПослеЗакрытияИсточникаМенеджерНеРаботает() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытый пул"); + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + + ТекстОшибки = ""; + Попытка + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Обращение через закрытый источник завершается ошибкой").Содержит("закрыт"); + +КонецПроцедуры + +&Тест +Процедура ИсточникЗакрываетсяПоЗавершениюПотока() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникВДругомПотоке", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Источник закрыт по завершению создавшего его потока") + .ЭтоИстина(); + +КонецПроцедуры + +&Тест +Процедура АвтоЗакрытиеОтключаетсяСеттером() Экспорт + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьИсточникБезАвтоЗакрытияВДругомПотоке", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем.Что(ИсточникДругогоПотока.Закрыт(), "Без автоматического закрытия источник переживает поток").ЭтоЛожь(); + + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); + Ожидаем.Что(Менеджер.ВычислитьСКоннектором("Коннектор -> ТипЗнч(Коннектор)")).Равно(Тип("КоннекторInMemory")); + + ИсточникДругогоПотока.Закрыть(); + +КонецПроцедуры + +// Обработчик завершения потока - один из многих: движок обходит подписчиков события в порядке +// подписки и ошибки обработчиков не перехватывает. Выпусти наш обработчик исключение наружу, и +// подписавшиеся позже остались бы необработанными: источники того же потока не закрылись бы, +// а пулы не получили назад соединения завершившегося потока. +&Тест +Процедура ОшибкаЗакрытияНеЛишаетАвтоЗакрытияОстальныеИсточники() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), + "КоннекторУправляемоеОткрытие" + ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "СоздатьДваИсточникаВДругомПотоке", Новый Массив, Истина); + Задание.ОжидатьЗавершения(); + + Ожидаем.Что(Задание.ИнформацияОбОшибке = Неопределено, "Поток создания завершился без ошибок").ЭтоИстина(); + Ожидаем + .Что(ВторойИсточникДругогоПотока.Закрыт(), "Источник, подписавшийся после падающего, закрыт") + .ЭтоИстина(); + +КонецПроцедуры + +// Хранилище выдается только для типа, зарегистрированного в модели менеджера: без модели хранилище +// не работает, а падало бы оно уже внутри операции и не о том. Несозданное хранилище и в кэш не +// попадает, поэтому после регистрации класса менеджер выдает рабочее. +&Тест +Процедура ХранилищеНезарегистрированногоТипаНеВыдается() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилище без модели"); + Менеджер = Новый МенеджерСущностей(Источник); + + ТекстОшибки = ""; + Попытка + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем + .Что(ТекстОшибки, "Хранилище незарегистрированного типа не выдается") + .Содержит("не зарегистрирован в модели данных"); + + Менеджер.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); + Менеджер.ДобавитьКлассВМодель(Тип("Автор")); + Менеджер.Инициализировать(); + + Ожидаем + .Что(Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")).ПолучитьОбъектМодели() = Неопределено, + "После регистрации класса менеджер выдает хранилище с моделью") + .ЭтоЛожь(); + + Менеджер.Закрыть(); + Источник.Закрыть(); + +КонецПроцедуры + +// Закрытие менеджера терминально: хранилищ он больше не выдает. Иначе поток, ожидавший создания +// хранилища, создал бы его уже после закрытия, и это хранилище пережило бы менеджер вместе с его +// очищенной моделью. +&Тест +Процедура ЗакрытыйМенеджерХранилищНеВыдает() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища после закрытия"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Менеджер.Закрыть(); + + ТекстОшибкиХранилища = ""; + Попытка + Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Исключение + ТекстОшибкиХранилища = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + ТекстОшибкиЧтения = ""; + Попытка + Менеджер.Получить(Тип("Автор")); + Исключение + ТекстОшибкиЧтения = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибкиХранилища, "Закрытый менеджер хранилище не выдает").Содержит("закрыт"); + Ожидаем.Что(ТекстОшибкиЧтения, "Чтение через закрытый менеджер завершается ошибкой").Содержит("закрыт"); + + Источник.Закрыть(); + +КонецПроцедуры + +// Источник считается закрытым только после того, как пул закрыт: до тех пор закрытие еще может +// упасть и его придется повторять. Выставь признак раньше - и второй закрывающий ушел бы молча, +// решив, что дело сделано, пока соединения еще живы. +&Тест +Процедура ИсточникСчитаетсяЗакрытымТолькоПослеЗакрытияПула() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), + "КоннекторУправляемоеОткрытие" + ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НаблюдательЗакрытияИсточника.os"), + "НаблюдательЗакрытияИсточника" + ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); + + Наблюдатель = Новый НаблюдательЗакрытияИсточника; + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.НаблюдательЗакрытия = Наблюдатель; + + Источник = Новый ИсточникДанных( + "Основной", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора() + ); + Наблюдатель.УстановитьИсточник(Источник); + + // Соединение открывается, иначе закрывать в пуле будет нечего и наблюдателя никто не спросит. + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + + Ожидаем + .Что(Наблюдатель.ИсточникСчиталсяЗакрытым(), "Посреди закрытия источник закрытым еще не считается") + .ЭтоЛожь(); + Ожидаем.Что(Источник.Закрыт(), "После закрытия пула источник закрыт").ЭтоИстина(); + +КонецПроцедуры + +// Закрытый менеджер не работает совсем, а не только по части хранилищ: иначе модель можно было бы +// набрать заново и создать по ней таблицы, а транзакция потока открылась бы такому менеджеру, +// который ни одной операции над данными в нее уже не пропустит. Работают только геттеры и повторное +// закрытие. +&Тест +Процедура ЗакрытыйМенеджерРаботатьОтказывается() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "работа после закрытия"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Менеджер.Закрыть(); + + ПараметрыКлассаВМодель = Новый Массив; + ПараметрыКлассаВМодель.Добавить(Тип("Автор")); + + ПараметрыАлгоритма = Новый Массив; + ПараметрыАлгоритма.Добавить("Коннектор -> Коннектор"); + + Ожидаем.Что(Менеджер, "Класс в модель закрытого менеджера не добавляется") + .Метод("ДобавитьКлассВМодель", ПараметрыКлассаВМодель) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Таблицы по модели закрытого менеджера не создаются") + .Метод("Инициализировать") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не начинается") + .Метод("НачатьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не фиксируется") + .Метод("ЗафиксироватьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Транзакция через закрытый менеджер не отменяется") + .Метод("ОтменитьТранзакцию") + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Прямой доступ к БД через закрытый менеджер закрыт") + .Метод("ВыполнитьСКоннектором", ПараметрыАлгоритма) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Вычисление коннектором через закрытый менеджер закрыто") + .Метод("ВычислитьСКоннектором", ПараметрыАлгоритма) + .ВыбрасываетИсключение("закрыт"); + + ПараметрыСущности = Новый Массив; + ПараметрыСущности.Добавить(Новый Автор); + + ПараметрыТипаСущности = Новый Массив; + ПараметрыТипаСущности.Добавить(Тип("Автор")); + + Ожидаем.Что(Менеджер, "Сохранение через закрытый менеджер не идет") + .Метод("Сохранить", ПараметрыСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Выборка через закрытый менеджер не идет") + .Метод("Получить", ПараметрыТипаСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Выборка одной сущности через закрытый менеджер не идет") + .Метод("ПолучитьОдно", ПараметрыТипаСущности) + .ВыбрасываетИсключение("закрыт"); + + Ожидаем.Что(Менеджер, "Удаление через закрытый менеджер не идет") + .Метод("Удалить", ПараметрыСущности) + .ВыбрасываетИсключение("закрыт"); + + Источник.Закрыть(); + +КонецПроцедуры + +// Хранилища принадлежат менеджеру, поэтому закрытие менеджера их и закрывает: прочитанные +// сущности из пула сущностей уходят. +&Тест +Процедура ЗакрытиеМенеджераЗакрываетЕгоХранилища() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "закрытие хранилищ"); + Менеджер = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Автор = Менеджер.СоздатьЭлемент(Тип("Автор")); + Автор.Имя = "Иван"; + Менеджер.Сохранить(Автор); + + Хранилище = Менеджер.ПолучитьХранилищеСущностей(Тип("Автор")); + Ожидаем.Что(Хранилище.ПолучитьПулСущностей().Количество(), "Сохраненная сущность попала в пул сущностей") + .Равно(1); + + Менеджер.Закрыть(); + + Ожидаем.Что(Хранилище.ПолучитьПулСущностей().Количество(), "Закрытие менеджера очистило пул сущностей") + .Равно(0); + + Источник.Закрыть(); + +КонецПроцедуры + +// Хранилище работает с моделью данных своего менеджера, поэтому и принадлежит менеджеру: у двух +// менеджеров одного источника хранилища разные, как и пулы сущностей в них. +&Тест +Процедура ХранилищаПринадлежатМенеджеруАНеИсточнику() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "хранилища по менеджеру"); + Первый = ТестовыеУтилиты.МенеджерАвторов(Источник); + Второй = ТестовыеУтилиты.МенеджерАвторов(Источник); + + Ожидаем + .Что(Первый.ПолучитьХранилищеСущностей(Тип("Автор")) = Второй.ПолучитьХранилищеСущностей(Тип("Автор")), + "У каждого менеджера свое хранилище") + .ЭтоЛожь(); + + Источник.Закрыть(); + +КонецПроцедуры + +// Закрытие менеджера очищает его модель данных. Пока хранилища принадлежали источнику, хранилище +// с моделью закрытого менеджера доставалось следующему менеджеру, и разыменование ссылки в нем +// уже не находило тип. +&Тест +Процедура ЗакрытиеМенеджераНеЛомаетДругойМенеджерИсточника() Экспорт + + ТестовыеУтилиты.ПодключитьКлассыСущностей(); + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "чужая модель"); + + Первый = ТестовыеУтилиты.МенеджерАвторов(Источник); + Внешняя = Первый.СоздатьЭлемент(Тип("СущностьБезГенерируемогоИдентификатора")); + Внешняя.ВнутреннийИдентификатор = 1; + Первый.Сохранить(Внешняя); + Автор = Первый.СоздатьЭлемент(Тип("Автор")); + Автор.Имя = "Иван"; + Автор.ВнешняяСущность = Внешняя; + Первый.Сохранить(Автор); + + Второй = ТестовыеУтилиты.МенеджерАвторов(Источник); + ХранилищеВторого = Второй.ПолучитьХранилищеСущностей(Тип("Автор")); + + Первый.Закрыть(); + + Ожидаем + .Что(ХранилищеВторого.Получить(), "Чтение со ссылкой переживает закрытие чужого менеджера") + .ИмеетДлину(1); + + Источник.Закрыть(); + +КонецПроцедуры + +&Тест +Процедура ПовторноеЗакрытиеИсточникаБезошибочно() Экспорт + + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторInMemory"), "двойное закрытие"); + Менеджер = Новый МенеджерСущностей(Источник); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + Источник.Закрыть(); + Источник.Закрыть(); + + Ожидаем.Что(Источник.Закрыт()).ЭтоИстина(); + +КонецПроцедуры + +// Создает источник и открывает соединение в потоке фонового задания. Используется тестом +// "ИсточникЗакрываетсяПоЗавершениюПотока". +// +Процедура СоздатьИсточникВДругомПотоке() Экспорт + + ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "другой поток"); + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + +КонецПроцедуры + +// Создает в потоке фонового задания два источника: первый на коннекторе, который падает при +// закрытии, второй обычный. Используется тестом +// "ОшибкаЗакрытияНеЛишаетАвтоЗакрытияОстальныеИсточники". +// +// Соединение первому источнику открывается намеренно: без открытого соединения закрывать в пуле +// было бы нечего и ошибка закрытия не возникла бы. +// +Процедура СоздатьДваИсточникаВДругомПотоке() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + + ИсточникДругогоПотока = Новый ИсточникДанных( + "Падающий при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора() + ); + МенеджерПадающего = Новый МенеджерСущностей(ИсточникДругогоПотока); + МенеджерПадающего.ВычислитьСКоннектором("Коннектор -> Коннектор"); + + ВторойИсточникДругогоПотока = Новый ИсточникДанных("Второй", Тип("КоннекторInMemory"), "второй источник потока"); + +КонецПроцедуры + +// Создает источник без автоматического закрытия и открывает соединение в потоке фонового задания. Используется +// тестом "АвтоЗакрытиеОтключаетсяСеттером". +// +Процедура СоздатьИсточникБезАвтоЗакрытияВДругомПотоке() Экспорт + + ИсточникДругогоПотока = Новый ИсточникДанных("Другой поток", Тип("КоннекторInMemory"), "без автоматического закрытия"); + ИсточникДругогоПотока.УстановитьАвтоЗакрытие(Ложь); + Менеджер = Новый МенеджерСущностей(ИсточникДругогоПотока); + Менеджер.ВычислитьСКоннектором("Коннектор -> Коннектор"); + +КонецПроцедуры diff --git "a/tests/\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/tests/\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 5db8be5d..eda97f70 100644 --- "a/tests/\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/tests/\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" @@ -9,6 +9,8 @@ #Использовать ".." #Использовать "utils" +#Использовать moskito + // Пул, который тесты создают заново перед каждым тестом. Перем Пул; // Пул с пределом в одно соединение для проверки ожидания. Виден фоновому заданию. @@ -25,6 +27,8 @@ Перем СбрасыватьТаблицы; // Соединение, удерживающее базу SQLite в памяти живой. Не задано для PostgreSQL. Перем ДержательБазыВПамяти; +// Признак того, что подписчик завершения потока, вставший в очередь после пула, был вызван. +Перем ПодписчикПослеПулаВызван; &ПередКаждым Процедура ПередЗапускомТеста() Экспорт @@ -36,6 +40,14 @@ ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторУправляемоеОткрытие.os"), "КоннекторУправляемоеОткрытие" ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "ПланЗакрытийКоннектора.os"), + "ПланЗакрытийКоннектора" + ); + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НастройкиТестовогоКоннектора.os"), + "НастройкиТестовогоКоннектора" + ); ОпределитьТестовуюБазу(); @@ -142,17 +154,24 @@ &Тест Процедура НулевойРазмерОтключаетПереиспользование() Экспорт - Менеджер = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияТестовойБазы, Неопределено, 0); + ИсточникБезПереиспользования = Новый ИсточникДанных( + "Без переиспользования", + ТипКоннектораТестовойБазы, + СтрокаСоединенияТестовойБазы, + , + 0 + ); + Менеджер = Новый МенеджерСущностей(ИсточникБезПереиспользования); Попытка ОчиститьТестовуюБазу(Менеджер); ПроверитьРаботуБезПереиспользования(Менеджер); Исключение - Менеджер.Закрыть(); + ИсточникБезПереиспользования.Закрыть(); ВызватьИсключение; КонецПопытки; - Менеджер.Закрыть(); + ИсточникБезПереиспользования.Закрыть(); КонецПроцедуры @@ -179,7 +198,7 @@ Менеджер.НачатьТранзакцию(); Ожидаем - .Что(Менеджер.ПолучитьПулСоединений().СоединениеТекущегоПотокаИсполнения() <> Неопределено, + .Что(Менеджер.ИсточникДанных().ПулСоединений().СоединениеТекущегоПотокаИсполнения() <> Неопределено, "Поток получил соединение и при нулевом размере") .ЭтоИстина(); @@ -380,22 +399,36 @@ КонецПроцедуры -// Задание оборвалось, не освободив соединение. Соединение возвращается в пул сразу по завершении +// Поток оборвался, не освободив соединение. Соединение возвращается в пул сразу по завершении // потока - иначе оно было бы потеряно навсегда. // +// Поток исполнения подставлен поддельный, а событие завершения поднимается явно: здесь проверяется +// реакция пула на завершение потока, а не умение движка обрывать фоновые задания. +// &Тест -Процедура СоединениеОборвавшегосяЗаданияВозвращаетсяВПул() Экспорт +Процедура СоединениеОборвавшегосяПотокаВозвращаетсяВПул() Экспорт - ПулСПределом = Новый ПулСоединенийСБД(1, Тип("КоннекторInMemory"), "", Новый Массив); + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулОборванного = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторInMemory"), + "", + Новый Массив, + 1000, + 10, + ПоддельныеПотоки + ); Попытка - ПроверитьВозвратПослеОбрыва(); + ПроверитьВозвратПослеОбрыва(ПулОборванного, ПоддельныеПотоки, ОборвавшийсяПоток); Исключение - ПулСПределом.Закрыть(); + ПулОборванного.Закрыть(); ВызватьИсключение; КонецПопытки; - ПулСПределом.Закрыть(); + ПулОборванного.Закрыть(); КонецПроцедуры @@ -417,38 +450,39 @@ КонецПроцедуры -// Проверяет, что соединение оборвавшегося задания достается следующему потоку. +// Проверяет, что соединение оборвавшегося потока достается следующему. +// +// Параметры: +// Пул - ПулСоединенийСБД - Пул с пределом в одно соединение. +// Потоки - ПотокиИсполнения - Поддельный поставщик текущего потока. +// ОборвавшийсяПоток - ПотокИсполнения - Поток, который займет соединение и завершится. // -Процедура ПроверитьВозвратПослеОбрыва() +Процедура ПроверитьВозвратПослеОбрыва(Пул, Потоки, ОборвавшийсяПоток) - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьИОборватьсяВФоне", Новый Массив, Истина); - ДождатьсяЗадания(Задание); + Потоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + Пул.Занять(); + + Ожидаем.Что(Пул.КоличествоЗанятых(), "Соединение закреплено за потоком").Равно(1); + + // Поток завершился, не освободив соединение: движок поднял бы это событие сам. + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); Ожидаем - .Что(Задание.ИнформацияОбОшибке <> Неопределено, "Задание оборвалось, не освободив соединение") - .ЭтоИстина(); - Ожидаем - .Что(ПулСПределом.КоличествоЗанятых(), "За оборвавшимся потоком соединение не осталось") + .Что(Пул.КоличествоЗанятых(), "За оборвавшимся потоком соединение не осталось") .Равно(0); Ожидаем - .Что(ПулСПределом.КоличествоСвободных(), "Соединение вернулось в пул сразу по завершении потока") + .Что(Пул.КоличествоСвободных(), "Соединение вернулось в пул сразу по завершении потока") .Равно(1); // Предел в одно соединение исчерпан: следующий поток получает брошенное без ожидания. - ПулСПределом.Занять(); - - Ожидаем.Что(ПулСПределом.КоличествоЗанятых(), "Соединение выдано текущему потоку").Равно(1); - Ожидаем.Что(ПулСПределом.КоличествоСвободных(), "Свободных не осталось").Равно(0); + Потоки.Когда().Текущий().ТогдаВозвращает(Мок.Получить(Тип("ПотокИсполнения"))); + Пул.Занять(); - ПулСПределом.Освободить(); + Ожидаем.Что(Пул.КоличествоЗанятых(), "Соединение выдано следующему потоку").Равно(1); + Ожидаем.Что(Пул.КоличествоСвободных(), "Свободных не осталось").Равно(0); -КонецПроцедуры + Пул.Освободить(); -// Занимает соединение и обрывается, не освободив его. Запускается в фоновом задании. -// -Процедура ЗанятьИОборватьсяВФоне() Экспорт - ПулСПределом.Занять(); - ВызватьИсключение "Обрыв задания с занятым соединением"; КонецПроцедуры // При нулевом размере освобожденное соединение закрывается, а не встает в очередь: следующий @@ -481,81 +515,20 @@ КонецПроцедуры -// Живой поток не должен терять соединение из-за очистки списка фоновых заданий: прежняя -// схема опроса переставала видеть живое задание и отбирала у него соединение. -// -&Тест -Процедура ОчисткаСпискаЗаданийНеОтбираетСоединениеУЖивогоПотокаИсполнения() Экспорт - - ПулСПределом = Новый ПулСоединенийСБД(1, Тип("КоннекторInMemory"), "", Новый Массив, 1000, 10); - - Попытка - - Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ДержатьСоединениеИПроверить", Новый Массив, Истина); - Приостановить(700); - - Ожидаем - .Что(ПулСПределом.КоличествоЗанятых(), "Задание заняло единственное соединение") - .Равно(1); - - ФоновыеЗадания.Очистить(); - - // Предел исчерпан. Основной поток просит соединение и получает отказ по таймауту - ОтказПолучен = Ложь; - Попытка - ПулСПределом.Занять(); - ПулСПределом.Освободить(); - Исключение - // Отказ ожидаем: единственное соединение занято живым заданием - ОтказПолучен = Истина; - КонецПопытки; - - Ожидаем - .Что(ОтказПолучен, "Соединение живого задания не отдано основному потоку") - .ЭтоИстина(); - - Задание.ОжидатьЗавершения(); - - Ожидаем - .Что(Задание.Результат, "За живым потоком осталось его соединение") - .ЭтоИстина(); - - Исключение - ПулСПределом.Закрыть(); - ВызватьИсключение; - КонецПопытки; - - ПулСПределом.Закрыть(); - -КонецПроцедуры - -// Занимает соединение, удерживает его и сообщает, осталось ли оно закрепленным. -// -// Возвращаемое значение: -// Булево - Истина, если соединение осталось за потоком. -// -Функция ДержатьСоединениеИПроверить() Экспорт - - ПулСПределом.Занять(); - Приостановить(2500); - Результат = ПулСПределом.ЕстьСоединениеТекущегоПотокаИсполнения(); - ПулСПределом.Освободить(); - - Возврат Результат; - -КонецФункции - // Открытие соединения - сетевой вызов, и держать на нем блокировку пула нельзя: иначе // недоступная база морозит всех, включая тех, кто хочет вернуть исправное соединение. // &Тест Процедура ОткрытиеСоединенияНеДержитБлокировкуПула() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПаузаОткрытия = 3000; + ПулМедленный = Новый ПулСоединенийСБД( 2, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(3000, Ложь) + НастройкиКоннектора.ПараметрыКоннектора() ); Попытка @@ -628,11 +601,14 @@ &Тест Процедура ЗакрытиеВоВремяОткрытияНеОставляетСоединения() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПаузаОткрытия = 3000; + ПулМедленный = Новый ПулСоединенийСБД( 1, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(3000, Ложь) + НастройкиКоннектора.ПараметрыКоннектора() ); Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьМедленноеВФоне", Новый Массив, Истина); @@ -657,11 +633,14 @@ &Тест Процедура НеудачноеОткрытиеВозвращаетМестоВПул() Экспорт + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриОткрытии = Истина; + ПулПадающий = Новый ПулСоединенийСБД( 1, Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Истина) + НастройкиКоннектора.ПараметрыКоннектора() ); Попытка @@ -690,20 +669,25 @@ &Тест Процедура ОшибкаЗавершенияТранзакцииНеОставляетСоединениеЗаПотокомИсполнения() Экспорт - ПулПадающий = Новый ПулСоединенийСБД( - 1, + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + + ИсточникПадающий = Новый ИсточникДанных( + "Падающий", Тип("КоннекторУправляемоеОткрытие"), "", - ПараметрыТестовогоКоннектора(0, Ложь, Истина) + НастройкиКоннектора.ПараметрыКоннектора(), + 1 ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); Попытка - РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ПулПадающий); + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ИсточникПадающий); Ожидаем .Что(РаботаСКоннекторами, "Фиксация завершилась ошибкой коннектора") - .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ПулПадающий)) + .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ИсточникПадающий)) .ВыбрасываетИсключение(); Ожидаем @@ -717,10 +701,272 @@ .Равно(0); Исключение - ПулПадающий.Закрыть(); + ИсточникПадающий.Закрыть(); ВызватьИсключение; КонецПопытки; + ИсточникПадающий.Закрыть(); + +КонецПроцедуры + +// Место непригодного соединения возвращается пулу до его закрытия: соединение уже отобрано у +// потока исполнения и больше нигде не числится, поэтому упади закрытие - место осталось бы занятым +// навсегда, и пул перестал бы выдавать соединения вовсе. +// +&Тест +Процедура ОшибкаЗакрытияНепригодногоСоединенияНеТеряетМестоВПуле() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + + ИсточникПадающий = Новый ИсточникДанных( + "Падающий и при транзакции, и при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1 + ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); + + РаботаСКоннекторами.НачатьТранзакциюПотокаИсполнения(ИсточникПадающий); + + // Фиксация не удалась - соединение непригодно, а его закрытие в пуле тоже падает. + Ожидаем + .Что(РаботаСКоннекторами, "Завершение транзакции завершилось ошибкой") + .Метод("ЗафиксироватьТранзакциюПотокаИсполнения", ПараметрыЗавершенияТранзакции(ИсточникПадающий)) + .ВыбрасываетИсключение(); + + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место непригодного соединения вернулось в пул") + .Равно(0); + +КонецПроцедуры + +// Транзакцию, которую поток исполнения не завершил сам, откатывает пул при возврате соединения. +// Неудача такого отката значит ровно то же, что и неудача явной фиксации или отмены: в каком +// состоянии осталась транзакция на стороне СУБД, неизвестно, и переиспользовать соединение нельзя. +// +&Тест +Процедура НеудачныйНеявныйОткатДелаетСоединениеНепригодным() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулПадающий = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + ПулПадающий.Занять().НачатьТранзакцию(); + + // Поток исполнения оборвался, не завершив транзакцию: откат делает сам пул при возврате. + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); + + Ожидаем + .Что(ПулПадающий.КоличествоСвободных(), "Соединение с незавершенной транзакцией в очередь свободных не встало") + .Равно(0); + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место непригодного соединения вернулось в пул") + .Равно(0); + +КонецПроцедуры + +// Обработчик завершения потока исполнения, встающий в очередь подписчиков после пула. Используется +// тестом "ОшибкаОсвобожденияНеОбрываетОстальныхПодписчиковЗавершенияПотока". +// +Процедура ОтметитьВызовПодписчикаПослеПула() Экспорт + ПодписчикПослеПулаВызван = Истина; +КонецПроцедуры + +// Освобождение ресурсов завершившегося потока исполнения ошибку наружу не выпускает. Подписчиков +// у завершения потока столько, сколько пулов и источников его застали, и движок обходит их одним +// циклом, не перехватывая ошибки обработчиков: выпусти этот обработчик исключение - и до +// подписавшихся позже очередь не дойдет. +// +&Тест +Процедура ОшибкаОсвобожденияНеОбрываетОстальныхПодписчиковЗавершенияПотока() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПадатьПриЗавершенииТранзакции = Истина; + НастройкиКоннектора.ПадатьПриЗакрытии = Истина; + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ОборвавшийсяПоток = Мок.Получить(Тип("ПотокИсполнения")); + + ПулПадающий = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ОборвавшийсяПоток); + + // Незавершенная транзакция делает соединение непригодным при возврате, а его закрытие падает: + // освобождение ресурсов потока исполнения натыкается на ошибку. + ПулПадающий.Занять().НачатьТранзакцию(); + + ПодписчикПослеПулаВызван = Ложь; + ДобавитьОбработчик ОборвавшийсяПоток.ПриЗавершении, ОтметитьВызовПодписчикаПослеПула; + + ОборвавшийсяПоток.ВызватьСобытие("ПриЗавершении", Новый Массив); + + Ожидаем + .Что(ПодписчикПослеПулаВызван, "Подписчик, вставший в очередь после пула, дождался вызова") + .ЭтоИстина(); + +КонецПроцедуры + +// Ошибка при закрытии соединения не должна делать источник закрытым: иначе повторный вызов +// ничего не сделает, и оставшиеся соединения некому закрыть. +// +&Тест +Процедура ОшибкаЗакрытияНеДелаетИсточникЗакрытым() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + + ИсточникПадающий = Новый ИсточникДанных( + "Падающий при закрытии", + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1 + ); + ПулПадающий = ИсточникПадающий.ПулСоединений(); + ПулПадающий.Занять(); + ПулПадающий.Освободить(); + + Ожидаем + .Что(ИсточникПадающий, "Закрытие завершилось ошибкой коннектора") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем.Что(ИсточникПадающий.Закрыт(), "Источник не считается закрытым после ошибки").ЭтоЛожь(); + + // Соединение, на котором споткнулось закрытие, осталось в пуле: повтор закрывает его же, и на + // этот раз удачно - по плану падает только первое закрытие. + ИсточникПадающий.Закрыть(); + + Ожидаем.Что(ИсточникПадающий.Закрыт(), "Повторное закрытие доводит дело до конца").ЭтоИстина(); + +КонецПроцедуры + +// Закрытие, упавшее на одном соединении, не должно оставить за потоками уже закрытые соединения: +// иначе поток получит из пула закрытое соединение и упадет на уровне драйвера, а не на понятной +// ошибке о закрытом пуле. +// +// Потоки исполнения подставлены поддельные, поэтому тест однопоточный: проверяется привязка +// "поток - соединение", а не конкуренция потоков. +// +&Тест +Процедура НеудачноеЗакрытиеНеОставляетЗакрытыеСоединенияЗаПотоками() Экспорт + + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + ПервыйПоток = Мок.Получить(Тип("ПотокИсполнения")); + ВторойПоток = Мок.Получить(Тип("ПотокИсполнения")); + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(2, 1); + + ПулПадающий = Новый ПулСоединенийСБД( + 2, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулПадающий.Занять(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + ПулПадающий.Занять(); + + // Второе по счету закрытие падает: пул закрытым не считается, закрытие можно повторить. + ОшибкаЗакрытия = ""; + Попытка + ПулПадающий.Закрыть(); + Исключение + ОшибкаЗакрытия = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем + .Что(ОшибкаЗакрытия, "Закрытие споткнулось на втором соединении") + .Содержит("Закрытие соединения не удалось"); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + Ожидаем + .Что(ПулПадающий.Занять().ПолучитьКоннектор().Открыт(), "Первому потоку досталось живое соединение") + .ЭтоИстина(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + Ожидаем + .Что(ПулПадающий.Занять().ПолучитьКоннектор().Открыт(), "Второму потоку досталось живое соединение") + .ЭтоИстина(); + + ПулПадающий.Закрыть(); + +КонецПроцедуры + +// Свободное соединение, на котором закрытие споткнулось, из очереди свободных не выбывает: иначе +// повтор закрытия его уже не найдет, отчитается об успехе, а соединение с БД останется открытым, +// и закрыть его будет некому. Занятые ведут себя так же - соединение остается за своим потоком. +// +// Потоки исполнения подставлены поддельные: два свободных соединения в одном потоке не получить, +// а проверяется здесь очередь свободных, а не конкуренция потоков. +// +&Тест +Процедура НеудачноеЗакрытиеНеТеряетСвободноеСоединение() Экспорт + + ПулПадающий = ПулСДвумяСвободнымиСоединениями(2); + + Ожидаем + .Что(ПулПадающий, "Закрытие споткнулось на втором свободном соединении") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем + .Что(ПулПадающий.КоличествоСвободных(), "Соединение, на котором упало закрытие, осталось в пуле") + .Равно(1); + + ПулПадающий.Закрыть(); + +КонецПроцедуры + +// Место закрытого соединения возвращается пулу сразу, как соединение закрыто: счетчик занятых +// мест считает живые соединения, а не когда-то открытые. Иначе после неудачного закрытия пул +// остается с местами, занятыми уже закрытыми соединениями, и следующий захват ждет впустую: +// вернуть соединение некому. +// +&Тест +Процедура НеудачноеЗакрытиеОсвобождаетМестоЗакрытогоСоединения() Экспорт + + ПулПадающий = ПулСДвумяСвободнымиСоединениями(2); + + // Первое соединение закрывается, на втором закрытие падает - что оно падает, проверяет соседний + // тест, здесь это лишь способ остановить закрытие на полпути. + Ожидаем.Что(ПулПадающий).Метод("Закрыть").ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем + .Что(ПулПадающий.КоличествоСозданных(), "Место закрытого соединения освобождено") + .Равно(1); + ПулПадающий.Закрыть(); КонецПроцедуры @@ -792,6 +1038,67 @@ КонецПроцедуры +// Соединение, назначенное заявке, при ошибке закрытия из очереди ожидающих не выбывает - как не +// выбывают свободное и занятое. Иначе повтор закрытия прошел бы мимо него, а соединение с БД +// осталось бы открытым. +// +&Тест +Процедура НеудачноеЗакрытиеНеТеряетСоединениеЗаявки() Экспорт + + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(1, 1); + + ПулСОжиданием = Новый ПулСоединенийСБД( + 1, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 30000, + 5000 + ); + + // Единственное соединение занято, поэтому фоновое задание встанет в очередь. + ПулСОжиданием.Занять(); + + Попытка + ПроверитьСохранностьСоединенияЗаявки(); + Исключение + ПулСОжиданием.Закрыть(); + ВызватьИсключение; + КонецПопытки; + +КонецПроцедуры + +// Отдает соединение заявке фонового задания и закрывает пул, пока задание спит между проверками +// своей заявки. +// +Процедура ПроверитьСохранностьСоединенияЗаявки() + + Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗанятьИзПулаСОжиданиемВФоне", Новый Массив, Истина); + + ДождатьсяОжидающего(); + + // Освобожденное соединение уходит заявке задания и до его пробуждения лежит только в очереди + // ожидающих: там его и находит закрытие пула. + ПулСОжиданием.Освободить(); + + Ожидаем + .Что(ПулСОжиданием, "Закрытие споткнулось на соединении заявки") + .Метод("Закрыть") + .ВыбрасываетИсключение("Закрытие соединения не удалось"); + + Ожидаем + .Что(ПулСОжиданием.КоличествоОжидающих(), "Заявка с незакрытым соединением осталась в очереди") + .Равно(1); + + // По плану падает только первое закрытие: повтор закрывает то же соединение и доводит дело + // до конца. + ПулСОжиданием.Закрыть(); + + ДождатьсяЗадания(Задание); + +КонецПроцедуры + // Ждет, пока фоновое задание встанет в очередь ожидающих. // Процедура ДождатьсяОжидающего() @@ -859,43 +1166,66 @@ ПулСОжиданием.Занять(); КонецПроцедуры -// Собирает параметры коннектора для КоннекторУправляемоеОткрытие. +// Пул на два соединения, оба занятые и сразу освобожденные: обе штуки лежат в очереди свободных. +// +// Потоки исполнения поддельные: захват реентерабельный, и в одном потоке второго соединения +// не получить. Предел ожидания короткий, чтобы тест не стоял на нем полминуты. // // Параметры: -// ПаузаОткрытия - Число - Пауза внутри открытия в миллисекундах. -// ПадатьПриОткрытии - Булево - Истина, если открытие должно завершиться ошибкой. -// ПадатьПриЗавершенииТранзакции - Булево - Истина, если фиксация и отмена должны падать. +// НомерПадающегоЗакрытия - Число - Номер закрытия, начиная с которого коннектор падает. // // Возвращаемое значение: -// Массив - Параметры коннектора. +// ПулСоединенийСБД - Пул с двумя свободными соединениями. // -Функция ПараметрыТестовогоКоннектора( - Знач ПаузаОткрытия, - Знач ПадатьПриОткрытии, - Знач ПадатьПриЗавершенииТранзакции = Ложь -) +Функция ПулСДвумяСвободнымиСоединениями(Знач НомерПадающегоЗакрытия) - Параметры = Новый Массив; - Параметры.Добавить(ПаузаОткрытия); - Параметры.Добавить(ПадатьПриОткрытии); - Параметры.Добавить(ПадатьПриЗавершенииТранзакции); + ПоддельныеПотоки = Мок.Получить(Тип("ПотокиИсполнения")); + // Падает одно закрытие: тест закрывает пул в конце, и вечно падающий коннектор ему бы этого + // не дал. + НастройкиКоннектора = Новый НастройкиТестовогоКоннектора; + НастройкиКоннектора.ПланЗакрытий = Новый ПланЗакрытийКоннектора(НомерПадающегоЗакрытия, 1); - Возврат Параметры; + ПулДвухСоединений = Новый ПулСоединенийСБД( + 2, + Тип("КоннекторУправляемоеОткрытие"), + "", + НастройкиКоннектора.ПараметрыКоннектора(), + 1000, + 10, + ПоддельныеПотоки + ); + + ПервыйПоток = Мок.Получить(Тип("ПотокИсполнения")); + ВторойПоток = Мок.Получить(Тип("ПотокИсполнения")); + + // Соединения занимаются оба сразу: освободи первый поток свое до захвата второго, второй взял + // бы то же самое из свободных, и второго соединения в пуле не появилось бы. + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулДвухСоединений.Занять(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ВторойПоток); + ПулДвухСоединений.Занять(); + ПулДвухСоединений.Освободить(); + + ПоддельныеПотоки.Когда().Текущий().ТогдаВозвращает(ПервыйПоток); + ПулДвухСоединений.Освободить(); + + Возврат ПулДвухСоединений; КонецФункции // Параметры вызова ЗафиксироватьТранзакциюПотокаИсполнения. // // Параметры: -// Пул - ПулСоединений - Пул, которому принадлежит соединение. +// Источник - ИсточникДанных - Источник, которому принадлежит соединение. // // Возвращаемое значение: // Массив - Параметры метода. // -Функция ПараметрыЗавершенияТранзакции(Пул) +Функция ПараметрыЗавершенияТранзакции(Источник) Параметры = Новый Массив; - Параметры.Добавить(Пул); + Параметры.Добавить(Источник); Возврат Параметры; 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 b57f7246..e746521f 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" @@ -1,8 +1,8 @@ // Хранилище сущностей: чтение, запись и транзакции. // -// Транзакция принадлежит потоку исполнения, а не хранилищу: она открывается на соединении из -// пула менеджера и распространяется на все операции потока. Хранилище, созданное в обход -// менеджера, работает собственным коннектором. +// Транзакция принадлежит потоку исполнения, а не хранилищу: она открывается на соединении +// источника данных и распространяется на все операции потока через любое хранилище и менеджер +// этого источника. // // BSLLS:MagicNumber-off // BSLLS:LatinAndCyrillicSymbolInWord-off @@ -12,6 +12,8 @@ #Использовать ".." #Использовать "utils" +// ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. +Перем Источник; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Хранилище авторов, полученное из менеджера. @@ -39,7 +41,10 @@ ВызватьИсключение "Нет доступного коннектора для тестирования хранилища сущностей"; КонецЕсли; - МенеджерСущностей = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + Источник = Новый ИсточникДанных("Тесты", ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); Если ВыполнятьСбросТаблиц Тогда ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); @@ -69,6 +74,7 @@ Хранилище = Неопределено; МенеджерСущностей.Закрыть(); МенеджерСущностей = Неопределено; + Источник.Закрыть(); КонецПроцедуры &Тест @@ -211,11 +217,12 @@ КонецПроцедуры -// Создает менеджер, работает с сущностью и закрывает его. +// Создает источник и менеджер, работает с сущностью и закрывает их. // Процедура ПрожитьЦиклМенеджера() - Менеджер = Новый МенеджерСущностей(ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + ИсточникЦикла = Новый ИсточникДанных("Цикл", ТипКоннектораТестовойБазы, СтрокаСоединенияКоннектора); + Менеджер = Новый МенеджерСущностей(ИсточникЦикла); Попытка @@ -228,10 +235,12 @@ Исключение Менеджер.Закрыть(); + ИсточникЦикла.Закрыть(); ВызватьИсключение; КонецПопытки; Менеджер.Закрыть(); + ИсточникЦикла.Закрыть(); КонецПроцедуры