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 @@
Исключение
Менеджер.Закрыть();
+ ИсточникЦикла.Закрыть();
ВызватьИсключение;
КонецПопытки;
Менеджер.Закрыть();
+ ИсточникЦикла.Закрыть();
КонецПроцедуры