Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Классы/АбстрактныйКоннекторSQL.os
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@

КонецФункции

// Выполняет подготовленную вставку, сообщая о ней наблюдателям, и возвращает идентификатор
// вставленной строки.
//
// Идентификатор читается до рассылки события вставки: он относится к соединению, а наблюдатель,
// пишущий в ту же базу, работает тем же соединением, и его вставка подменила бы идентификатор.
//
// Параметры:
// Запрос - Запрос - Подготовленный запрос вставки.
// ИмяТаблицы - Строка - Таблица вставки.
//
// Возвращаемое значение:
// Число - Идентификатор вставленной строки.
//
Функция ВставитьСНаблюдением(Запрос, Знач ИмяТаблицы) Экспорт

Событие = НачатьЗапрос("INSERT", ИмяТаблицы, Запрос.Текст);

Попытка
Запрос.ВыполнитьКоманду();
Идентификатор = Коннектор.КонструкторКоннектора.ИДПоследнейДобавленнойЗаписи(Коннектор.Соединение, Запрос);
Исключение
ЗавершитьЗапрос(Событие, ИнформацияОбОшибке());
ВызватьИсключение;
КонецПопытки;

ЗавершитьЗапрос(Событие);

Возврат Идентификатор;

КонецФункции

Функция НачатьЗапрос(Знач ОперацияСУБД, Знач ИмяТаблицы, Знач ТекстЗапроса)

Если Наблюдатели = Неопределено Тогда
Expand Down
9 changes: 6 additions & 3 deletions src/Классы/КоннекторSQLite.os
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,23 @@
Лог.Отладка("Сохранение сущности с типом %1:%2%3", ОбъектМодели.ТипСущности(), Символы.ПС, ТекстЗапроса);

Запрос.Текст = ТекстЗапроса;
КоннекторSQL.ВыполнитьСНаблюдением(Запрос, "INSERT", ИмяТаблицы, Ложь);

// Идентификатор объекта перезаписывается только тогда, когда его выдала СУБД. Если он был
// задан вызывающим кодом, объект уже несет верное значение.
//
// last_insert_rowid относится к соединению, а соединение занято одним потоком исполнения,
// поэтому вставка и получение идентификатора не разъезжаются и без внешней блокировки.
// поэтому вставку и получение идентификатора не разделит другой поток. Разделить их мог бы
// наблюдатель вставки, пишущий в ту же базу тем же соединением, поэтому идентификатор читается
// до рассылки события, см. ВставитьСНаблюдением.
Если СоздаватьНовыйИдентификатор Тогда
ИДПоследнейДобавленнойЗаписи = КонструкторКоннектора.ИДПоследнейДобавленнойЗаписи(Соединение, Запрос);
ИДПоследнейДобавленнойЗаписи = КоннекторSQL.ВставитьСНаблюдением(Запрос, ИмяТаблицы);
ОбъектМодели.УстановитьЗначениеКолонкиВПоле(
Сущность,
ОбъектМодели.Идентификатор().ИмяКолонки,
ИДПоследнейДобавленнойЗаписи
);
Иначе
КоннекторSQL.ВыполнитьСНаблюдением(Запрос, "INSERT", ИмяТаблицы, Ложь);
КонецЕсли;

// TODO: Для полей с автоинкрементом - получить значения из базы.
Expand Down
75 changes: 75 additions & 0 deletions tests/КоннекторSQLite.os
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
// ИсточникДанных - Источник менеджера набора: создается в подготовке, закрывается после теста.
Перем Источник;
Перем Коннектор;
// МенеджерСущностей - Менеджер теста записи журнала наблюдателем; см.
// ЗаписьНаблюдателяПослеВставкиНеСдвигаетИдентификатор.
Перем МенеджерЖурнала;

&ВключенЕсли("() -> ЗапускатьТестыSQLite()")
&ТестовыйНабор
Expand Down Expand Up @@ -563,3 +566,75 @@
Ожидаем.Что(НайденныеСтроки, "Повторное сохранение обновило строку, а не добавило").ИмеетДлину(2);

КонецПроцедуры

// Наблюдатель, который после вставки пишет журнал в ту же базу, работает тем же соединением и
// своей вставкой сдвигает last_insert_rowid. Сохраненная сущность должна получить свой
// идентификатор, а не идентификатор строки журнала.
//
&Тест
Процедура ЗаписьНаблюдателяПослеВставкиНеСдвигаетИдентификатор() Экспорт

ПодключитьСценарий(
ОбъединитьПути(ТекущийКаталог(), "tests", "fixtures", "НаблюдательСДействием.os"),
"НаблюдательСДействием"
);

// Пул из одного соединения: база в памяти своя у каждого соединения.
ИсточникЖурнала = Новый ИсточникДанных("Основной", Тип("КоннекторSQLite"), "Data Source=:memory:", , 1);
ИсточникЖурнала.УстановитьАвтоЗакрытие(Ложь);

Попытка
ПроверитьИдентификаторПриЗаписиНаблюдателя(ИсточникЖурнала);
Исключение
Ошибка = ИнформацияОбОшибке();
ИсточникЖурнала.Закрыть();
ВызватьИсключение;
КонецПопытки;

ИсточникЖурнала.Закрыть();

КонецПроцедуры

// Сохраняет автора, пока наблюдатель пишет после вставки строку журнала, и проверяет
// идентификатор автора.
//
// Параметры:
// ИсточникЖурнала - ИсточникДанных - Источник с базой в памяти.
//
Процедура ПроверитьИдентификаторПриЗаписиНаблюдателя(ИсточникЖурнала)

МенеджерЖурнала = ТестовыеУтилиты.МенеджерАвторов(ИсточникЖурнала);

// Наблюдатель зовет действие в потоке теста, поэтому поля набора ему доступны.
Наблюдатель = Новый НаблюдательСДействием(
"Запрос",
"INSERT",
"После",
Новый Действие(ЭтотОбъект, "ЗаписатьЖурнал"),
Истина
);
ИсточникЖурнала.ДобавитьНаблюдателя(Наблюдатель);

Автор = Новый Автор;
Автор.Имя = "Пушкин";
МенеджерЖурнала.Сохранить(Автор);

Ожидаем.Что(Наблюдатель.Выполнено(), "Наблюдатель записал журнал").Равно(1);
Ожидаем.Что(Наблюдатель.Ошибки(), "Запись журнала прошла").ИмеетДлину(0);

СтрокиАвторов = ТестовыеУтилиты.ВыполнитьЗапрос(МенеджерЖурнала, "SELECT Идентификатор FROM Авторы");
Ожидаем.Что(СтрокиАвторов, "Автор сохранен").ИмеетДлину(1);
Ожидаем.Что(Автор.ВнутреннийИдентификатор, "Автор получил идентификатор своей строки")
.Равно(СтрокиАвторов[0].Идентификатор);

КонецПроцедуры

// Сохраняет строку журнала менеджером теста. Действие наблюдателя.
//
Процедура ЗаписатьЖурнал() Экспорт

Журнал = Новый СущностьБезГенерируемогоИдентификатора;
Журнал.ВнутреннийИдентификатор = 1000;
МенеджерЖурнала.Сохранить(Журнал);

КонецПроцедуры
Loading