Серверная логика
Запрос как хранимая процедура
Запрос в Интеграме не только читает данные, но и меняет их: колонка с выражением «Присвоить» записывает значение в каждую отобранную строку, создаёт или удаляет записи. Логика хранится в самой базе, а роли получают право вызвать запрос, а не право писать в таблицы. Это аналог хранимой процедуры: изменения идут только так, как их описал автор запроса, и только в тех строках, которые он разрешил.
Здесь — как собрать такой запрос, вызвать его, ограничить круг вызывающих и строки, которые им доступны, и сделать вызов атомарным. Примеры даны для https://ideav.ru и базы mydb; $TOKEN и $XSRF — токены из справочника API.
Как это устроено
Зачем процедура вместо прав на таблицу
Право WRITE на таблицу разрешает менять любое поле любой доступной записи. Часто нужно другое: менеджер видит статус заказа, но ставит «Согласован» только кнопкой, только в своих заказах, и вместе со статусом обязательно пишется дата согласования. Такое правило не выразить уровнем доступа — его выражают запросом.
- поле в роли — только чтение, менять его может лишь запрос;
- роль получает доступ READ к самому запросу — этого достаточно, чтобы его вызвать;
- запрос сам решает, какие строки трогать и что в них писать: фильтры внутри запроса вызывающий не переопределит;
- каждое изменение попадает в журнал, а с галкой InTransaction вызов выполняется целиком или не выполняется вовсе.
Что проверяется при вызове
| Проверка | Где задаётся |
|---|---|
| Может ли пользователь вызвать запрос | Доступ роли к запросу (таблица 116): без него — ответ 403 |
| Какие строки будут изменены | Фильтры колонок запроса (t102/t103), в том числе [USER_ID], [ROLE_ID] |
| Что будет записано | Выражения «Присвоить» (t132) — их задаёт только автор запроса |
| Что может подставить вызывающий | Только фильтры колонок с «Именем в отчёте» (t100) и условие WHERE, которое лишь сужает выборку |
| Что изменение намеренное | POST с confirmed=1 и _xsrf; без них — только превью |
Права роли на таблицы при выполнении запроса не проверяются: запрос пишет туда, куда его направил автор. Поэтому вся защита — в определении запроса, и править его могут только администраторы базы.
Собрать запрос-процедуру
Колонка «Присвоить»
У колонки запроса есть поле «Присвоить» (t132) — выражение, которое вычисляется для каждой отобранной строки и записывается в эту колонку. Запрос, у которого есть хотя бы одно такое выражение, — изменяющий.
| Выражение в t132 | Что запишется |
|---|---|
'Согласован' | Строка — в SQL-кавычках |
[TODAY], [NOW], [USER_ID], [USER] | Дата, время, id или логин вызывающего |
[THIS] + 1, substring([THIS],1,10) | Выражение от текущего значения колонки |
CONCAT(Имя, ' ', Фамилия) | SQL-выражение над псевдонимами других колонок (t101) |
12345 в колонке-справочнике | Ссылка на запись справочника по id |
[Тонер,картридж] в мультиссылке | Набор ссылок: каждое значение ищется в справочнике по тексту |
| пустое значение | Удаление значения, а у колонки главной таблицы — удаление записи |
Текст в колонке-справочнике ищется по названию, и отсутствующее значение создаётся. Опечатка в выражении заведёт в справочнике новую запись — надёжнее писать id.
Пример: согласование заказа
Запрос approve_order: ставит заказу статус «Согласован» и дату согласования. Менеджер может согласовать только свой заказ и только один за вызов. Коды для примера: таблица Заказ — 900, её колонки Статус — 901, Дата согласования — 902, Менеджер (ссылка на пользователя) — 903, роль «Менеджер» — 164.
# сам запрос
curl -s "https://ideav.ru/mydb/_m_new/22?JSON=1" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" -F "up=1" -F "t22=approve_order"
# → {"id": 950}
# 1. какой заказ: параметр order_id, без него не отбирается ни одна строка
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=950" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=950" -F "t28=900" -F "t100=order_id" -F "t104=abn_ID" -F "t102=0"
# 2–3. что записать
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=950" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=950" -F "t28=901" -F "t100=Статус" -F "t132='Согласован'"
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=950" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=950" -F "t28=902" -F "t100=Дата согласования" -F "t132=[TODAY]"
# 4. ограничитель: только заказы вызывающего. Без t100 — снаружи не переопределить
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=950" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=950" -F "t28=903" -F "t104=abn_ID" -F "t102=[USER_ID]" -F "t107=1"
# 5. роли «Менеджер» — доступ READ к запросу
curl -s "https://ideav.ru/mydb/_m_new/116?JSON=1" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=164" -F "t116=950" -F "t136=53"
Поля Статус и Дата согласования роли выдают на чтение (t136=53): руками их не поменять, только этим запросом. Так закрепляется бизнес-правило — «согласовать» значит пройти через кнопку со всеми её условиями.
Ограничители, которые не подменить
Снаружи фильтруются только колонки с заполненным «Именем в отчёте» (t100): параметры FR_{имя} и TO_{имя} перекрывают их t102/t103. Фильтр колонки без имени параметрами адреса не меняется — это и есть защита от подмены фильтра вызывающим.
- ограничитель — колонка без
t100, обычно скрытая (t107=1):[USER_ID]на колонке-ссылке на пользователя, статус, дата; - параметр — колонка с
t100: то, что вызывающий вправе выбирать, например id заказа; - значение параметра понимает синтаксис фильтров:
FR_order_id=%отберёт все заказы,>0— тоже. Поэтому охват всегда держит ограничитель без имени, а не параметр; - условие
WHEREиз адреса вызова встаёт в выборку в скобках какAND (…)и может только сузить её. Строка с лишней закрывающей скобкой, незакрытыми кавычками,;или комментарием отклоняется.
Не давайте колонке-ограничителю «Имя в отчёте». С именем её фильтр становится параметром: вызывающий подставит чужой id вместо [USER_ID] и изменит чужие строки.
Повторный вызов без вреда
Охват изменения — строки под фильтрами. Добавьте фильтр «ещё не обработано», и повторный прогон не тронет уже изменённые строки: например, скрытая колонка результата с t102=!% (пусто). Так безопасно перезапускать массовые обработки — нормализацию названий, разметку, пересчёт.
# колонка «Токены» (731): заполнять только пустые; Название — псевдоним (t101) другой колонки
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=960" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=960" -F "t28=731" -F "t102=!%" \
-F "t132=LOWER(Название)"
Вызов
Превью: что изменится
Вызов изменяющего запроса без подтверждения ничего не пишет. В интерфейсе отчёта он показывает строки под фильтром и ссылку «Выполнить»; через API — те же строки с текущими значениями.
curl -s -H "X-Authorization: $TOKEN" "https://ideav.ru/mydb/report/approve_order?JSON_KV&FR_order_id=1234"
Применение: confirmed=1
Изменения применяются POST-запросом с полем confirmed=1 ко всем строкам под фильтром. Подтверждённый вызов проверяет _xsrf — и при авторизации токеном тоже: без него сервер отвечает 403 «Неверный или устаревший токен CSRF». Ответ — строки уже с новыми значениями.
curl -s "https://ideav.ru/mydb/report/approve_order?JSON_KV" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "confirmed=1" -F "FR_order_id=1234"
Кнопка в рабочем месте делает то же самое: POST на report/<запрос> с телом confirmed=1&FR_order_id=…&_xsrf=….
EXECUTE: без подтверждения
Булев реквизит запроса EXECUTE снимает оба шага: запрос применяет изменения при любом вызове, GET или POST, без confirmed и без проверки XSRF. Встроенный в рабочее место EXECUTE-запрос срабатывает при каждом открытии страницы — это автоматизация без кнопки.
Реквизит распознаётся по имени. Если у таблицы «Запрос» его нет, его добавляют как обычную булеву колонку EXECUTE.
EXECUTE пишет данные при каждом вызове, в том числе по ссылке. Для изменений, которые нельзя выполнить случайно, оставляйте двухшаговый вызов с confirmed=1.
Кто и как вызывает
- кнопка или действие рабочего места — POST с
confirmed=1; - открытие страницы с встроенным EXECUTE-запросом;
- внешняя система по расписанию — своей учётной записью с доступом к одному запросу, а не к таблицам;
- ИИ-агент и интеграции — через тот же
report/своей учётной записью и с правами её роли; - триггер кнопки при создании, изменении или удалении записи — см. «Триггеры кнопок».
Права вызывающего
Кто может вызвать
Вызов разрешён роли с доступом к запросу — записью таблицы 116: t116={id запроса}, t136=53 (READ). Доступ ко всей таблице «Запрос» (22) открывает все запросы сразу. Без доступа сервер отвечает 403; пользователь admin проходит всегда.
curl -s "https://ideav.ru/mydb/_m_new/116?JSON=1" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=164" -F "t116=950" -F "t136=53"
Условия по пользователю и роли
Встроенные переменные подставляются значениями вызывающего в любой фильтр и выражение запроса — так проверка прав живёт внутри процедуры:
| Переменная | Значение |
|---|---|
[USER_ID], [USER] | id и логин вызывающего |
[ROLE_ID], [ROLE] | id и название его роли; [ROLE] в формуле берут в кавычки: '[ROLE]' |
[TODAY], [NOW], [MONTH_AGO] | дата и время вызова |
[Имя запроса] | значение другого запроса — например, список разрешённых id |
«Только свои записи» — ограничитель t102=[USER_ID] на колонке-ссылке на пользователя (см. пример выше). «Только для ролей» — скрытая вычисляемая колонка-условие без имени: для чужой роли запрос не отберёт ни одной строки и ничего не изменит.
# менять могут только роли 164 и 170
curl -s "https://ideav.ru/mydb/_m_new/28?JSON=1&up=950" \
-H "X-Authorization: $TOKEN" -F "_xsrf=$XSRF" \
-F "up=950" -F "t28=0" -F "t101=[ROLE_ID] IN (164,170)" -F "t102=1" -F "t107=1"
Поле только для чтения, меняет кнопка
Чтобы поле менялось только процедурой, роли выдают на него READ (t136=53), а запрос с «Присвоить» на это поле — READ на сам запрос. Так закрепляются статусы, даты согласования, суммы к оплате: правило проверки — в запросе, обойти его правкой карточки нельзя.
Кто правит сами процедуры
Определение запроса — записи таблиц «Запрос» (22) и «Колонки запроса» (28). Кто может их менять, тот может написать любое «Присвоить» в любое поле. Ролям пользователей WRITE на таблицы 22 и 28 не выдают: процедуры правят только администраторы базы.
Целостность
InTransaction: всё или ничего
Булев реквизит запроса InTransaction выполняет вызов одной транзакцией: выборка и все её изменения — присвоения, создание и удаление записей — фиксируются вместе, а ошибка посреди вызова откатывает уже записанное.
- действует только на применение изменений: у запроса есть «Присвоить», и вызов идёт с
confirmed=1или с EXECUTE; превью и чистая выборка идут без транзакции; - вложенный запрос, вызванный внутри открытой транзакции, свою не открывает;
- реквизит распознаётся по имени; если у таблицы «Запрос» его нет, добавьте булеву колонку
InTransaction, как иEXECUTE.
Без галки каждая запись фиксируется сразу, и упавший вызов оставляет сделанное до ошибки. Тогда выручают фильтр «ещё не обработано» и журнал.
Журнал изменений
Каждый изменяющий SQL попадает в журнал базы <база>_sql.jsonl с состоянием до и после, пользователем и временем. Администратор базы читает его командой journal.
curl -s -H "X-Authorization: $TOKEN" "https://ideav.ru/mydb/journal?JSON&mode=sql"
Журнал хранит и строки изменений, откатившихся по InTransaction.
Внешние вызовы из запроса
Колонка-функция abn_URL и реквизиты запроса URL, URL_POST, URL_HEADER делают HTTP-вызов внешнему сервису на каждую строку выборки, подставляя значения колонок. Ответ читается формулой CURL.<ключ> и тем же «Присвоить» пишется в запись.
Транзакция охватывает только базу: внешний вызов не откатывается, даже если откатились изменения. Обращения к localhost из запросов запрещены.
На практике
Чек-лист процедуры
- все условия охвата — колонки без «Имени в отчёте»; с именем — только то, что вызывающий вправе выбирать;
- параметр без значения по умолчанию не отбирает ничего (
t102=0у колонки id), а не всё; - ограничитель по вызывающему:
[USER_ID]на ссылке на пользователя или условие по[ROLE_ID]; - поле, которое меняет процедура, у роли — только чтение;
- роли — READ на запрос; WRITE на таблицы 22 и 28 — только администраторам;
- опасные изменения — через
confirmed=1, EXECUTE — только там, где прогон при каждом вызове задуман; - изменения, которые нельзя оставить наполовину, — с InTransaction; массовые — с фильтром «ещё не обработано»;
- перед вызовом с
confirmed=1посмотрите превью тем же адресом без него.
Грабли
- пустой результат «Присвоить» удаляет значение, а у колонки главной таблицы — запись;
- пунктуация внутри значения мультиссылки разрывает его на части: разделитель — любой символ, кроме букв, цифр и пробела;
- текст в колонке-справочнике с опечаткой создаёт новое значение справочника;
- EXECUTE включается самим наличием значения реквизита, даже
0; у InTransaction0значит «выключено».
Смотрите также
- справочник API — создание запросов, фильтры, формулы, подзапросы, доступы ролей:
/api.html; - триггеры кнопок — запуск запроса при создании, изменении и удалении записи:
/triggers.html; - база знаний, статья 29 — «Запрос как хранимая процедура»: зачем модель и чем она отличается от прав роли.