Внешняя компонента для 1С:Предприятие 8 для выполнения запросов к postgresql и получения уведомлений.
- Написана на языке
RUST
- blazing fast, memory safe и т.д.)) - Выполнена по технологии
Native API
- Кроссплатформена - Linux + Windows.
- Собирается с помощью свободных инструментов,
msvc
не нужен.
- На первом этапе реализованы только простые запросы. Это запросы которые нельзя параметризовать, могут иметь несколько операторов разделенных
;
и возвращают данные в текстовом виде, подробнее см. https://postgrespro.ru/docs/postgresql/15/protocol-flow#id-1.10.6.7.4 - В компоненте реализовано получение уведомлений, сгенерированных командой
NOTIFY
. Основной сценарий - получение уведомлений об изменении данных в нужных таблицах. Это позволяет сделать простой брокер на postgresql (имеет смысл в случае не очень большой нагрузки). Либо для оперативного получения событий изменения данных в 1С, т.к. уведомления будут отправлены только после завершения транзакции. В этом случае, предполагается, что будет всегда запущено фоновое задание, которое будет слушать эти события. Подробности см. https://postgrespro.ru/docs/postgresql/15/sql-notify. - Все методы компоненты всегда являются функциями, даже если возвращать ничего не нужено, то будет возвращено
Неопределено
. Это нужно, чтобы можно было что-то вычислить в отладчике. - Если при вызове метода или при обращении к свойству компоненты возникает исключение, то текст исключения должен быть в свойстве
LastError
. Эта особенность из-за api внешних компонент, которое не позволяет передать текст исключения. Свойство сбрасывается при вызове любых методов а также при записи любого свойства. - Т.к. api внешних компонент не позволяет передавать массивы значений и объекты, то в случае такой необходимости будет возвращаться
Json
как объектДвоичныеДанные
в кодировкеUTF-8
. Именно такой формат обусловлен тем, чтоRUST
хранит свои строки в кодировкеUTF-8
, а1С
в кодировкеUTF-16
, таким образом такой формат требует меньше лишних вычислений.
LastError
- Строка - возвращает последнюю ошибку, в случае когда будет брошено исключено.Connected
- Булево - возвращает Истина если активно подключение.
Connect(СтрокаПодключения: Строка): Неопределено
- в случае неуспешного подключения будет брошено исключение, ошибку можно посмотреть в свойствеLastError
.SimpleQuery(Запрос: Строка): ДвоичныеДанные
- выполняет простой запрос, результат возвращается как Json в бинарном виде в кодировке utf-8.Notifications(Таймаут: Число): ДвоичныеДанные
- получает уведомления, перед этим нужно выполнить один или несколько запросовLISTEN
. Таймаут задает ожидание в миллисекундах.
Процедура ПолучениеУведомлений()
СтрокаСоединения = "host=/var/run/postgresql port=5432 dbname=test user=postgres application_name=AddinPostgres";
ИмяФайла = "/var/1C/obmen/libaddin_postgres.so";
Если Не ПодключитьВнешнююКомпоненту(ИмяФайла, "Test", ТипВнешнейКомпоненты.Native, ТипПодключенияВнешнейКомпоненты.НеИзолированно) Тогда
ВызватьИсключение "Не удалось подключить внешнюю компоненту";
КонецЕсли;
Postgres = Новый ("Addin.Test.Postgres");
Попытка
Postgres.Connect(СтрокаСоединения);
ТекстЗапросаФункции =
"CREATE OR REPLACE FUNCTION notify()
|RETURNS TRIGGER AS
|$$
|BEGIN
| PERFORM pg_notify(TG_TABLE_NAME, '');
| RETURN NEW;
|END;
|$$
|LANGUAGE PLPGSQL";
ШаблонТриггера =
"CREATE OR REPLACE TRIGGER notify
|AFTER INSERT OR UPDATE OR DELETE ON %1
|EXECUTE FUNCTION notify();";
Запросы = Новый Массив;
Запросы.Добавить(ТекстЗапросаФункции);
Для Каждого Справочник Из Метаданные.Справочники Цикл
ОбъектыМетаданных = Новый Массив;
ОбъектыМетаданных.Добавить(Справочник);
ИменаТаблиц = ПолучитьСтруктуруХраненияБазыДанных(ОбъектыМетаданных, Истина);
ИмяТаблицы = ИменаТаблиц.Найти("Основная", "Назначение").ИмяТаблицыХранения;
Запросы.Добавить(СтрШаблон(ШаблонТриггера, ИмяТаблицы));
Запросы.Добавить(СтрШаблон("LISTEN %1", ИмяТаблицы));
КонецЦикла;
ТекстЗапроса = СтрСоединить(Запросы, Символы.ПС + ";" + Символы.ПС);
Postgres.SimpleQuery(ТекстЗапроса);
Пока Истина Цикл
Результат = Postgres.Notifications(5000);
Уведомления = JsonВОбъект(Результат);
Если Уведомления.Количество() = 0 Тогда
Продолжить;
КонецЕсли;
Строки = Новый Массив;
Для Каждого Уведомление Из Уведомления Цикл
Строки.Добавить(Уведомление.Channel);
КонецЦикла;
ЗаписьЖурналаРегистрации("Отладка", , , , СтрШаблон("Получены уведомления: %1", СтрСоединить(Строки, ",")));
Если НеобходимостьЗавершенияСоединения().НеобходимоЗавершить Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Исключение
Если Не ПустаяСтрока(Postgres.LastError) Тогда
ВызватьИсключение Postgres.LastError;
КонецЕсли;
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Функция JsonВОбъект(ДвоичныеДанные)
ЧтениеJSON = Новый ЧтениеJSON();
ЧтениеJSON.ОткрытьПоток(ДвоичныеДанные.ОткрытьПотокДляЧтения());
Данные = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат Данные;
КонецФункции
В 1С нет события ПослеТранзакции, но оно требуется чтобы например оперативно отправить измененные данные. В таком случае можно в регламентном задании слушать события изменения нужных таблиц и оперативно реагировать. Кто-то скажет, что постоянно запущенное фоновое задание это антипаттерн, но при использовании 1С:Шина тоже постоянно запущено фоновое задание, поэтому считаю такой вариант официально рекомендуемым.
Многие хотят использовать брокер и эта компонента позволяет сделать это довольно дешево. Но такой вариант подходит для простых случаев и небольшой нагрузки. Примерная схема выглядит так: в базе создается таблица для сообщений, поставщики пишут туда свои сообщения, а потребитель при получении уведомлений вычитывает их из этой таблицы. В таком случае, триггер для уведомления имеет смысл делать только для события INSERT.
В этом варианте не нужно создвать ни таблиц, ни триггеров. Имеет смысл только создать пустую базу и пользователя, который имеет доступ только к этой базе. В одном сеансе можно слушать сообщения:
Postgres.SimpleQuery("LISTEN my_channel");
Пока Истина Цикл
Результат = Postgres.Notifications(5000);
Уведомления = JsonВОбъект(Результат.Значение);
Для Каждого Уведомление Из Уведомления Цикл
ОбработкаСообщения(Уведомление.Payload);
КонецЦикла;
КонецЦикла;
Из других сеансов отправлять сообщения:
Postgres.SimpleQuery("NOTIFY my_channel, 'This is the payload'");