diff --git a/README.md b/README.md index 09f7894..681c040 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,8 @@ Например, `КоннекторJSON` не умеет работать с транзакциями, однако, он имеет соответствующие методы, выводящие диагностические сообщения при их вызове. +Коннектор с транзакциями **обязан** сам знать, открыта ли транзакция, и отвечать на это методом `ВТранзакции()`: транзакцию может начать текстом запроса и бросить алгоритм `ВыполнитьСКоннектором`, и пул откатывает ее по этому ответу, прежде чем соединение достанется другому потоку. Коннектор без транзакций отвечает `Ложь`. + Коннектор **может** дополнительно реализовать интерфейс [`НаблюдаемыйКоннектор`](docs/НаблюдаемыйКоннектор.md): тогда наблюдатели источника данных получают события его запросов к СУБД и описание соединения. Встроенные коннекторы реализуют оба интерфейса. Коннектор без второго интерфейса работает как раньше. > Важно! diff --git "a/docs/\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.md" "b/docs/\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.md" index 77049d4..bb16689 100644 --- "a/docs/\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.md" +++ "b/docs/\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.md" @@ -64,6 +64,25 @@ Процедура ОтменитьТранзакцию() Экспорт ``` +### ВТранзакции + +Коннектор с транзакциями ведет признак сам: транзакцию может начать и бросить алгоритм +`ВыполнитьСКоннектором` мимо библиотеки, и узнать о ней пул может только у коннектора. Коннектор +без транзакций отвечает `Ложь`. + +```bsl +// Определяет, начата ли этим коннектором транзакция, которая еще не завершена: методом +// коннектора или текстом запроса прямого доступа. +// +// По ответу пул откатывает транзакцию, брошенную алгоритмом ВыполнитьСКоннектором, прежде чем +// соединение достанется другому потоку исполнения. Коннектор без транзакций отвечает Ложь. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт +``` + ### ИнициализироватьТаблицу ```bsl diff --git "a/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.md" "b/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.md" index 6dc1e1e..c2e8148 100644 --- "a/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.md" +++ "b/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.md" @@ -64,6 +64,17 @@ Процедура ОтменитьТранзакцию() Экспорт ``` +### ВТранзакции + +```bsl +// Определяет, начата ли транзакция. Транзакций коннектор не поддерживает. +// +// Возвращаемое значение: +// Булево - Всегда Ложь. +// +Функция ВТранзакции() Экспорт +``` + ### ИнициализироватьТаблицу ```bsl diff --git "a/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.md" "b/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.md" index 0322401..a3a44aa 100644 --- "a/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.md" +++ "b/docs/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.md" @@ -64,6 +64,18 @@ Процедура ОтменитьТранзакцию() Экспорт ``` +### ВТранзакции + +```bsl +// Определяет, начата ли этим коннектором транзакция, которая еще не завершена: методом +// коннектора или текстом запроса. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт +``` + ### ИнициализироватьТаблицу ```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 c2994f8..48a2145 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" @@ -158,6 +158,32 @@ PostgreSQL это закрывает параметр строки соедин Если текущий контекст исполнения начал транзакцию, алгоритм получает коннектор этой транзакции и попадает в нее. +Транзакцию для алгоритма открывайте менеджером: `МенеджерСущностей.НачатьТранзакцию()` до +`ВыполнитьСКоннектором`, фиксация или отмена - после. Транзакция, которую алгоритм начал сам - +методом коннектора или запросом `BEGIN`, - и не завершил, откатывается при возврате соединения в +пул: иначе она ушла бы с соединением к другому контексту, и его фиксация записала бы строки +алгоритма. Наблюдатели увидят запрос `ROLLBACK`, события транзакции у нее нет: начинала ее не +библиотека. + +```bsl +// Удаление не применится: транзакция брошена алгоритмом и откатывается при возврате соединения +МенеджерСущностей.ВыполнитьСКоннектором( + "Коннектор -> Коннектор.ВыполнитьЗапрос(""BEGIN""); Коннектор.ВыполнитьЗапрос(Текст)", + Новый Структура("Текст", "DELETE FROM Авторы") +); +``` + +О транзакции коннектор узнает по первому оператору каждого выполненного запроса: `BEGIN` и +`START TRANSACTION` ее начинают, `COMMIT`, `END` и `ROLLBACK` завершают, откат к точке сохранения +(`ROLLBACK TO`) не завершает. Не видны ему транзакции, начатые или завершенные вторым оператором +многооператорного текста, процедурой на стороне СУБД, а в SQLite - еще и `SAVEPOINT` вне транзакции +и `RELEASE` последней точки сохранения. О транзакции пулу сообщает сам коннектор методом +`ВТранзакции()` интерфейса `АбстрактныйКоннектор`: сторонний коннектор с транзакциями должен вести +этот признак сам. + +Коннектор не храните вне алгоритма: после выхода из него соединение принадлежит пулу и может +работать в другом контексте. + Прежний `ПолучитьКоннектор()` удален: он отдавал соединение наружу, не имея способа получить его обратно, поэтому соединение оставалось за контекстом навсегда. 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 2b88363..16b6c9d 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" @@ -209,9 +209,10 @@ // Отмечает возврат соединения в пул. // // Незавершенная транзакция при этом откатывается: соединение уходит следующему потоку, и -// оставлять на нем чужую начатую транзакцию нельзя. Ошибка отката наружу не идет - соединение -// возвращается в пул в любом случае, иначе оно было бы потеряно, - но соединение после нее -// непригодно так же, как после неудачного явного завершения транзакции. +// оставлять на нем чужую начатую транзакцию нельзя. Откатывается и транзакция, которую начал и +// бросил алгоритм прямого доступа мимо этого соединения: о ней знает коннектор. Ошибка отката +// наружу не идет - соединение возвращается в пул в любом случае, иначе оно было бы потеряно, - но +// соединение после нее непригодно так же, как после неудачного явного завершения транзакции. // Процедура ОтметитьВозврат() Экспорт @@ -227,6 +228,8 @@ КонецПопытки; ВТранзакции = Ложь; ЗавершитьВерхнееСобытиеТранзакции("abandoned", ОшибкаОтката); + Иначе + ОткатитьТранзакциюКоннектора(); КонецЕсли; // Остаток стопки - события начал, которым не досталось своего завершения: их закрывает @@ -239,6 +242,25 @@ КонецПроцедуры +// Откатывает транзакцию, которую начал мимо этого соединения и не завершил алгоритм прямого +// доступа. +// +// Начинала ее не библиотека, поэтому события транзакции у нее нет: наблюдатели увидят только +// запрос отката. Если коннектор не смог ответить или откатить, состояние транзакции неизвестно и +// соединение непригодно. +// +Процедура ОткатитьТранзакциюКоннектора() + + Попытка + Если Коннектор.ВТранзакции() Тогда + РаботаСКоннекторами.ОтменитьТранзакцию(Коннектор); + КонецЕсли; + Исключение + ИспортитьПослеНеудачногоЗавершения(); + КонецПопытки; + +КонецПроцедуры + // Закрывает соединение. // Процедура Закрыть() Экспорт diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" index 9fdf764..4a61c7c 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200.os" @@ -53,9 +53,22 @@ // Отменяет открытую транзакцию в БД. // Процедура ОтменитьТранзакцию() Экспорт - + КонецПроцедуры +// Определяет, начата ли этим коннектором транзакция, которая еще не завершена: методом +// коннектора или текстом запроса прямого доступа. +// +// По ответу пул откатывает транзакцию, брошенную алгоритмом ВыполнитьСКоннектором, прежде чем +// соединение достанется другому потоку исполнения. Коннектор без транзакций отвечает Ложь. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт + Возврат Ложь; +КонецФункции + // Создает таблицу в БД по данным модели. // // Параметры: diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" index 388e077..c3b8c41 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\220\320\261\321\201\321\202\321\200\320\260\320\272\321\202\320\275\321\213\320\271\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQL.os" @@ -6,10 +6,14 @@ // НаблюдателиИсточникаДанных - Наблюдатели пула, которым сообщается о каждом запросе. Неопределено, // пока коннектор работает вне пула. Перем Наблюдатели; +// Булево - Транзакция начата запросом этого коннектора и еще не завершена, см. ОтметитьТранзакцию. +// Соединение занято одним потоком исполнения, поэтому признак читает и пишет только он. +Перем ТранзакцияОткрыта; Процедура ПриСозданииОбъекта(пКоннектор, пЛог = Неопределено) Коннектор = пКоннектор; + ТранзакцияОткрыта = Ложь; Если ЗначениеЗаполнено(пЛог) Тогда Лог = пЛог; Иначе @@ -59,6 +63,7 @@ ВызватьИсключение; КонецПопытки; + ОтметитьТранзакцию(ОперацияСУБД, Запрос.Текст); ЗавершитьЗапрос(Событие, , ?(ВернутьРезультат, Результат.Количество(), Неопределено)); Возврат Результат; @@ -85,6 +90,83 @@ КонецПроцедуры +// Определяет, начата ли запросом этого коннектора транзакция, которая еще не завершена. +// +// Признак знает и о транзакции, начатой мимо библиотеки: методом коннектора или текстом запроса +// в прямом доступе. Пул по нему откатывает транзакцию, брошенную алгоритмом, прежде чем отдать +// соединение другому потоку исполнения. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт + Возврат ТранзакцияОткрыта; +КонецФункции + +// Отмечает начало или завершение транзакции успешно выполненным запросом. +// +// Начало - BEGIN и START TRANSACTION, завершение - COMMIT, END и ROLLBACK; откат к точке +// сохранения транзакцию не завершает. Неудавшийся запрос признак не меняет: в каком состоянии +// осталась транзакция, неизвестно, и лишний откат при возврате соединения в пул безопаснее, чем +// пропущенный. +// +// Признак ведется по первому оператору текста. Транзакцию, завершенную вторым оператором +// многооператорного текста или процедурой на стороне СУБД, он не видит. +// +// Параметры: +// ОперацияСУБД - Строка - Операция выполненного запроса. +// ТекстЗапроса - Строка - Текст выполненного запроса. +// +Процедура ОтметитьТранзакцию(Знач ОперацияСУБД, Знач ТекстЗапроса) + + Если ОперацияСУБД = "BEGIN" ИЛИ ОперацияСУБД = "START" Тогда + ТранзакцияОткрыта = Истина; + ИначеЕсли ОперацияСУБД = "COMMIT" ИЛИ ОперацияСУБД = "END" Тогда + ТранзакцияОткрыта = Ложь; + ИначеЕсли ОперацияСУБД = "ROLLBACK" И НЕ ЭтоОткатКТочкеСохранения(ТекстЗапроса) Тогда + ТранзакцияОткрыта = Ложь; + КонецЕсли; + +КонецПроцедуры + +// Определяет, откатывает ли ROLLBACK к точке сохранения: ROLLBACK [WORK | TRANSACTION] TO ... +// +// Параметры: +// ТекстЗапроса - Строка - Текст запроса ROLLBACK. +// +// Возвращаемое значение: +// Булево - Истина, если откат к точке сохранения. +// +Функция ЭтоОткатКТочкеСохранения(Знач ТекстЗапроса) + + Слова = СловаЗапроса(ТекстЗапроса); + + НомерСлова = 1; + Если Слова.Количество() > НомерСлова И (Слова[НомерСлова] = "WORK" ИЛИ Слова[НомерСлова] = "TRANSACTION") Тогда + НомерСлова = НомерСлова + 1; + КонецЕсли; + + Возврат Слова.Количество() > НомерСлова И Слова[НомерСлова] = "TO"; + +КонецФункции + +// Разбивает текст запроса на слова в верхнем регистре. Разделители - пробельные символы и точка +// с запятой. +// +// Параметры: +// ТекстЗапроса - Строка - Текст запроса или команды. +// +// Возвращаемое значение: +// Массив из Строка - Слова текста. +// +Функция СловаЗапроса(Знач ТекстЗапроса) + + Разделители = " ;" + Символы.Таб + Символы.ПС + Символы.ВК; + + Возврат СтрРазделить(ВРег(ТекстЗапроса), Разделители, Ложь); + +КонецФункции + // Определяет операцию СУБД по первым словам текста запроса: SELECT, DELETE, CREATE TABLE и т.п. // // Параметры: @@ -95,15 +177,15 @@ // Функция ОперацияИзТекста(Знач ТекстЗапроса) - Слова = СтрРазделить(СокрЛП(СтрЗаменить(ТекстЗапроса, Символы.ПС, " ")), " ", Ложь); + Слова = СловаЗапроса(ТекстЗапроса); Если Слова.Количество() = 0 Тогда Возврат ""; КонецЕсли; - Операция = ВРег(Слова[0]); + Операция = Слова[0]; ОперацияНадСхемой = Операция = "CREATE" ИЛИ Операция = "DROP" ИЛИ Операция = "ALTER"; Если ОперацияНадСхемой И Слова.Количество() > 1 Тогда - Операция = Операция + " " + ВРег(Слова[1]); + Операция = Операция + " " + Слова[1]; КонецЕсли; Возврат Операция; diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" index 8b5c9d4..e9c6cae 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200InMemory.os" @@ -93,6 +93,15 @@ ВызватьИсключение "Не поддерживается"; КонецПроцедуры +// Определяет, начата ли транзакция. Транзакций коннектор не поддерживает. +// +// Возвращаемое значение: +// Булево - Всегда Ложь. +// +Функция ВТранзакции() Экспорт + Возврат Ложь; +КонецФункции + // Создает таблицу в БД по данным модели. // // Параметры: diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" index a0498f6..722d6fb 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200JSON.os" @@ -132,6 +132,15 @@ ВызватьИсключение "Не поддерживается"; КонецПроцедуры +// Определяет, начата ли транзакция. Транзакций коннектор не поддерживает. +// +// Возвращаемое значение: +// Булево - Всегда Ложь. +// +Функция ВТранзакции() Экспорт + Возврат Ложь; +КонецФункции + // Создает таблицу в БД по данным модели. // // Параметры: diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" index 30e0b43..eec8b81 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200PostgreSQL.os" @@ -100,6 +100,16 @@ ВыполнитьКоманду("ROLLBACK", "ROLLBACK;"); КонецПроцедуры +// Определяет, начата ли этим коннектором транзакция, которая еще не завершена: методом +// коннектора или текстом запроса. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт + Возврат КоннекторSQL.ВТранзакции(); +КонецФункции + // Выполняет команду без таблицы, сообщая о ней наблюдателям. // // Параметры: diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" index 6a22b4b..f165a43 100644 --- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" +++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200SQLite.os" @@ -100,6 +100,16 @@ ВыполнитьКоманду("ROLLBACK", "ROLLBACK TRANSACTION;"); КонецПроцедуры +// Определяет, начата ли этим коннектором транзакция, которая еще не завершена: методом +// коннектора или текстом запроса. +// +// Возвращаемое значение: +// Булево - Истина, если транзакция начата и не завершена. +// +Функция ВТранзакции() Экспорт + Возврат КоннекторSQL.ВТранзакции(); +КонецФункции + // Выполняет команду без таблицы, сообщая о ней наблюдателям. // // Параметры: diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" index 9293797..c8e0633 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\235\320\260\320\261\320\273\321\216\320\264\320\265\320\275\320\270\321\217.os" @@ -34,6 +34,10 @@ Внутренний.ОтменитьТранзакцию(); КонецПроцедуры +Функция ВТранзакции() Экспорт + Возврат Внутренний.ВТранзакции(); +КонецФункции + Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт Внутренний.ИнициализироватьТаблицу(ОбъектМодели); КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\237\321\200\320\270\320\267\320\275\320\260\320\272\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\270.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\237\321\200\320\270\320\267\320\275\320\260\320\272\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\270.os" new file mode 100644 index 0000000..a934db7 --- /dev/null +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\221\320\265\320\267\320\237\321\200\320\270\320\267\320\275\320\260\320\272\320\260\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\270.os" @@ -0,0 +1,44 @@ +// Коннектор, реализующий все методы АбстрактныйКоннектор, кроме ВТранзакции. Проверяет, что +// источник отвергает коннектор, не сообщающий о своей транзакции: пул не смог бы откатить +// транзакцию, брошенную алгоритмом прямого доступа. + +// BSLLS:UnusedParameters-off + +&Реализует("АбстрактныйКоннектор") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +Процедура Открыть(СтрокаСоединения, ПараметрыКоннектора) Экспорт +КонецПроцедуры + +Процедура Закрыть() Экспорт +КонецПроцедуры + +Функция Открыт() Экспорт + Возврат Истина; +КонецФункции + +Процедура НачатьТранзакцию() Экспорт +КонецПроцедуры + +Процедура ЗафиксироватьТранзакцию() Экспорт +КонецПроцедуры + +Процедура ОтменитьТранзакцию() Экспорт +КонецПроцедуры + +Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт +КонецПроцедуры + +Процедура Сохранить(ОбъектМодели, Сущность) Экспорт +КонецПроцедуры + +Процедура Удалить(ОбъектМодели, Сущность) Экспорт +КонецПроцедуры + +Функция НайтиСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт + Возврат Новый Массив; +КонецФункции + +Процедура УдалитьСтрокиВТаблице(ОбъектМодели, ОпцииПоиска) Экспорт +КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" index edae5cd..4536503 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\235\320\265\320\277\320\276\320\273\320\275\320\276\320\235\320\260\320\261\320\273\321\216\320\264\320\260\320\265\320\274\321\213\320\271.os" @@ -40,6 +40,10 @@ Внутренний.ОтменитьТранзакцию(); КонецПроцедуры +Функция ВТранзакции() Экспорт + Возврат Внутренний.ВТранзакции(); +КонецФункции + Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт Внутренний.ИнициализироватьТаблицу(ОбъектМодели); КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" index 622daf5..44fca9e 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\233\320\276\320\274\320\260\320\275\321\213\320\274\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\274.os" @@ -60,6 +60,10 @@ Внутренний.ОтменитьТранзакцию(); КонецПроцедуры +Функция ВТранзакции() Экспорт + Возврат Внутренний.ВТранзакции(); +КонецФункции + Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт Внутренний.ИнициализироватьТаблицу(ОбъектМодели); КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\274\320\237\321\200\320\270\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\270.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\274\320\237\321\200\320\270\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\270.os" index 5832a36..5032739 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\274\320\237\321\200\320\270\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\270.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\241\320\276\320\261\321\213\321\202\320\270\320\265\320\274\320\237\321\200\320\270\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\270.os" @@ -53,6 +53,10 @@ Внутренний.ОтменитьТранзакцию(); КонецПроцедуры +Функция ВТранзакции() Экспорт + Возврат Внутренний.ВТранзакции(); +КонецФункции + Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт Внутренний.ИнициализироватьТаблицу(ОбъектМодели); КонецПроцедуры diff --git "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" index dd71cc3..7439d0c 100644 --- "a/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" +++ "b/tests/fixtures/\320\232\320\276\320\275\320\275\320\265\320\272\321\202\320\276\321\200\320\242\321\200\320\260\320\275\320\267\320\260\320\272\321\206\320\270\320\271\320\224\320\273\321\217\320\242\320\265\321\201\321\202\320\276\320\262.os" @@ -105,6 +105,16 @@ КонецПроцедуры +// О своей транзакции коннектор не сообщает: он изображает транзакции для событий соединения, а +// откат брошенной алгоритмом транзакции проверяется на настоящих СУБД. +// +// Возвращаемое значение: +// Булево - Всегда Ложь. +// +Функция ВТранзакции() Экспорт + Возврат Ложь; +КонецФункции + Процедура ИнициализироватьТаблицу(ОбъектМодели) Экспорт Внутренний.ИнициализироватьТаблицу(ОбъектМодели); КонецПроцедуры 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 cf139d8..b781068 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" @@ -94,6 +94,15 @@ КонецЕсли; КонецПроцедуры +// Состояния транзакции коннектор не изображает, см. НачатьТранзакцию. +// +// Возвращаемое значение: +// Булево - Всегда Ложь. +// +Функция ВТранзакции() Экспорт + Возврат Ложь; +КонецФункции + // Закрывает соединение, падая, если так задано параметрами. // Процедура Закрыть() Экспорт 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 08483c2..3047e74 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" @@ -11,6 +11,10 @@ // ИсточникДанных - Источник тестовой базы; закрывается после каждого теста. Перем Источник; +// Тип - Тип коннектора тестовой базы. +Перем ТипКоннектора; +// Строка - Строка соединения с тестовой базой. +Перем СтрокаСоединения; // Менеджер, который тесты создают заново перед каждым тестом. Перем МенеджерСущностей; // Хранилище авторов, полученное из менеджера. @@ -39,15 +43,6 @@ ВызватьИсключение "Нет доступного коннектора для тестирования доступа коннектором"; КонецЕсли; - Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения); - // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. - Источник.УстановитьАвтоЗакрытие(Ложь); - МенеджерСущностей = Новый МенеджерСущностей(Источник); - - Если ВыполнятьСбросТаблиц Тогда - ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); - КонецЕсли; - ПодключитьСценарий( ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "Автор.os"), "Автор" @@ -57,13 +52,53 @@ "СущностьБезГенерируемогоИдентификатора" ); + СоздатьМенеджер(10, ВыполнятьСбросТаблиц); + + ИмяИзЗахваченногоОбъекта = "Захваченный"; + +КонецПроцедуры + +// Создает источник, менеджер с моделью авторов и хранилище авторов. +// +// Параметры: +// РазмерПула - Число - Размер пула соединений источника. +// ВыполнятьСбросТаблиц - Булево - Удалить таблицы базы до инициализации модели. +// +Процедура СоздатьМенеджер(Знач РазмерПула, Знач ВыполнятьСбросТаблиц = Ложь) + + Источник = Новый ИсточникДанных("Тесты", ТипКоннектора, СтрокаСоединения, , РазмерПула); + // oneunit исполняет подготовку и тест в разных потоках: источник живет до ПослеКаждого. + Источник.УстановитьАвтоЗакрытие(Ложь); + МенеджерСущностей = Новый МенеджерСущностей(Источник); + + Если ВыполнятьСбросТаблиц Тогда + ТестовыеУтилиты.СброситьТаблицы(МенеджерСущностей); + КонецЕсли; + МенеджерСущностей.ДобавитьКлассВМодель(Тип("СущностьБезГенерируемогоИдентификатора")); МенеджерСущностей.ДобавитьКлассВМодель(Тип("Автор")); МенеджерСущностей.Инициализировать(); Хранилище = МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("Автор")); - ИмяИзЗахваченногоОбъекта = "Захваченный"; + +КонецПроцедуры + +// Заменяет менеджер и источник теста новыми, с пулом из одного соединения: следующий захват +// гарантированно получит то же соединение, что и предыдущий. +// +// База SQLite в памяти живет, пока открыто хоть одно ее соединение. Новый источник открывается +// до закрытия прежнего, поэтому таблицы и строки переживают замену. +// +Процедура ЗаменитьМенеджерНаПулИзОдного() + + ПрежнийМенеджер = МенеджерСущностей; + ПрежнийИсточник = Источник; + + СоздатьМенеджер(1); + + ПрежнийМенеджер.Закрыть(); + ПрежнийИсточник.Закрыть(); КонецПроцедуры @@ -169,6 +204,62 @@ КонецПроцедуры +// Транзакция, которую алгоритм начал методом коннектора и не завершил, не уходит с соединением +// к другому потоку: иначе его фиксация записала бы и строки алгоритма. +// +&Тест +Процедура БрошеннаяАлгоритмомТранзакцияНеДостаетсяДругомуПотоку() Экспорт + ПроверитьОткатБрошеннойТранзакции("Коннектор -> Коннектор.НачатьТранзакцию(); Коннектор.ВыполнитьЗапрос(Запрос)"); +КонецПроцедуры + +// То же для транзакции, начатой текстом запроса. +// +&Тест +Процедура БрошеннаяЗапросомТранзакцияНеДостаетсяДругомуПотоку() Экспорт + ПроверитьОткатБрошеннойТранзакции( + "Коннектор -> Коннектор.ВыполнитьЗапрос(""BEGIN""); Коннектор.ВыполнитьЗапрос(Запрос)" + ); +КонецПроцедуры + +// Текст запроса узнается и в нижнем регистре, и с точкой с запятой. +// +&Тест +Процедура БрошеннаяЗапросомВНижнемРегистреТранзакцияНеДостаетсяДругомуПотоку() Экспорт + ПроверитьОткатБрошеннойТранзакции( + "Коннектор -> Коннектор.ВыполнитьЗапрос(""begin;""); Коннектор.ВыполнитьЗапрос(Запрос)" + ); +КонецПроцедуры + +// Откат к точке сохранения транзакцию не завершает: брошенная после него транзакция откатывается +// так же. +// +&Тест +Процедура ОткатКТочкеСохраненияНеЗавершаетБрошеннуюТранзакцию() Экспорт + ПроверитьОткатБрошеннойТранзакции( + "Коннектор -> Коннектор.ВыполнитьЗапрос(""BEGIN""); Коннектор.ВыполнитьЗапрос(Запрос); + |Коннектор.ВыполнитьЗапрос(""SAVEPOINT Точка""); Коннектор.ВыполнитьЗапрос(""ROLLBACK TO Точка"")" + ); +КонецПроцедуры + +// Алгоритм, начавший и сам завершивший транзакцию, ничего не теряет: его строка зафиксирована. +// +&Тест +Процедура ЗавершеннаяАлгоритмомТранзакцияСохраняется() Экспорт + + ЗаменитьМенеджерНаПулИзОдного(); + + МенеджерСущностей.ВыполнитьСКоннектором( + "Коннектор -> Коннектор.ВыполнитьЗапрос(""BEGIN""); Коннектор.ВыполнитьЗапрос(Запрос); + |Коннектор.ВыполнитьЗапрос(""commit;"")", + Новый Структура("Запрос", "INSERT INTO Авторы (Имя, Фамилия) VALUES ('Свой', 'Иванов')") + ); + + ЗафиксироватьАвтораВДругомПотоке("Чужой"); + + Ожидаем.Что(ИменаАвторов(), "Строки обоих потоков на месте").ИмеетДлину(2); + +КонецПроцедуры + // Ошибка внутри алгоритма не должна оставлять соединение занятым. // &Тест @@ -328,6 +419,83 @@ Возврат ТестовыеУтилиты.ВыполнитьЗапрос(МенеджерСущностей, "SELECT * FROM Авторы"); КонецФункции +// Читает имена всех авторов. +// +// Возвращаемое значение: +// Массив из Строка - Имена авторов. +// +Функция ИменаАвторов() + Возврат ПрочитатьАвторов().ВыгрузитьКолонку("Имя"); +КонецФункции + +// Выполняет алгоритм, бросающий начатую транзакцию со вставленной строкой, затем фиксирует свою +// транзакцию в другом потоке на том же соединении и проверяет, что в базе только строка +// другого потока. +// +// Параметры: +// Алгоритм - Строка - Лямбда-выражение: начинает транзакцию и выполняет запрос из контекста. +// +Процедура ПроверитьОткатБрошеннойТранзакции(Знач Алгоритм) + + ЗаменитьМенеджерНаПулИзОдного(); + + МенеджерСущностей.ВыполнитьСКоннектором( + Алгоритм, + Новый Структура("Запрос", "INSERT INTO Авторы (Имя, Фамилия) VALUES ('Брошенный', 'Иванов')") + ); + + ЗафиксироватьАвтораВДругомПотоке("Чужой"); + + Имена = ИменаАвторов(); + Ожидаем.Что(Имена, "Строка брошенной транзакции не зафиксирована чужой фиксацией").ИмеетДлину(1); + Ожидаем.Что(Имена[0], "Зафиксирована строка другого потока").Равно("Чужой"); + +КонецПроцедуры + +// Сохраняет автора в транзакции фонового задания и дожидается его. +// +// Параметры: +// Имя - Строка - Имя автора. +// +Процедура ЗафиксироватьАвтораВДругомПотоке(Знач Имя) + + Параметры = Новый Массив; + Параметры.Добавить(МенеджерСущностей); + Параметры.Добавить(Имя); + + Задания = Новый Массив; + Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗафиксироватьАвтораВФоне", Параметры, Истина)); + + ТестовыеУтилиты.ПроверитьЗадания(Задания); + +КонецПроцедуры + +// Сохраняет автора в своей транзакции. Выполняется фоновым заданием. +// +// Параметры: +// Менеджер - МенеджерСущностей - Менеджер теста. +// Имя - Строка - Имя автора. +// +Процедура ЗафиксироватьАвтораВФоне(Менеджер, Знач Имя) Экспорт + + Автор = Новый Автор; + Автор.Имя = Имя; + Автор.ВтороеИмя = "Петров"; + + Менеджер.НачатьТранзакцию(); + + Попытка + Менеджер.Сохранить(Автор); + Исключение + Ошибка = ИнформацияОбОшибке(); + Менеджер.ОтменитьТранзакцию(); + ВызватьИсключение; + КонецПопытки; + + Менеджер.ЗафиксироватьТранзакцию(); + +КонецПроцедуры + // Строка соединения с тестовой базой PostgreSQL. // // Возвращаемое значение: 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 40d7bd0..482df4d 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" @@ -34,6 +34,28 @@ КонецПроцедуры +// ВТранзакции - обязательный метод интерфейса: по нему пул откатывает транзакцию, брошенную +// алгоритмом прямого доступа. +&Тест +Процедура КоннекторБезПризнакаТранзакцииОтвергается() Экспорт + + ПодключитьСценарий( + ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "КоннекторБезПризнакаТранзакции.os"), + "КоннекторБезПризнакаТранзакции" + ); + + ТекстОшибки = ""; + Попытка + Источник = Новый ИсточникДанных("Основной", Тип("КоннекторБезПризнакаТранзакции")); // BSLLS:UnusedLocalVariable-off + Исключение + ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()); + КонецПопытки; + + Ожидаем.Что(ТекстОшибки, "Источник отвергает коннектор без ВТранзакции") + .Содержит("не реализует интерфейс коннектора"); + +КонецПроцедуры + // Пул создается вместе с источником, поэтому и размер пула проверяется при создании. &Тест Процедура НеверныйРазмерПулаОтвергаетсяПриСоздании() Экспорт