Skip to content

Latest commit

 

History

History
142 lines (110 loc) · 11.5 KB

README.md

File metadata and controls

142 lines (110 loc) · 11.5 KB

addin-postgres

Внешняя компонента для 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, а в кодировке UTF-16, таким образом такой формат требует меньше лишних вычислений.

API

Свойства

  • 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С нет события ПослеТранзакции, но оно требуется чтобы например оперативно отправить измененные данные. В таком случае можно в регламентном задании слушать события изменения нужных таблиц и оперативно реагировать. Кто-то скажет, что постоянно запущенное фоновое задание это антипаттерн, но при использовании 1С:Шина тоже постоянно запущено фоновое задание, поэтому считаю такой вариант официально рекомендуемым.

Простой брокер сообщений

Многие хотят использовать брокер и эта компонента позволяет сделать это довольно дешево. Но такой вариант подходит для простых случаев и небольшой нагрузки. Примерная схема выглядит так: в базе создается таблица для сообщений, поставщики пишут туда свои сообщения, а потребитель при получении уведомлений вычитывает их из этой таблицы. В таком случае, триггер для уведомления имеет смысл делать только для события INSERT.

Обмен сообщениями между сеансами

В этом варианте не нужно создвать ни таблиц, ни триггеров. Имеет смысл только создать пустую базу и пользователя, который имеет доступ только к этой базе. В одном сеансе можно слушать сообщения:

Postgres.SimpleQuery("LISTEN my_channel");
Пока Истина Цикл
    Результат = Postgres.Notifications(5000);
    Уведомления = JsonВОбъект(Результат.Значение); 
    Для Каждого Уведомление Из Уведомления Цикл
        ОбработкаСообщения(Уведомление.Payload);
    КонецЦикла;
КонецЦикла;

Из других сеансов отправлять сообщения:

Postgres.SimpleQuery("NOTIFY my_channel, 'This is the payload'");