Свои базы данных: Qubix.db
Qubix.db — команда, через которую пользовательский скрипт и обработчик страницы сайта работают с вашей собственной базой данных PostgreSQL, MySQL или MariaDB: читают строки и записывают изменения. Посредник по HTTP для этого не нужен — скрипт сам подключается к базе по строке соединения.
Это не база Qubix. Статистику, которую записывает Qubix, читает команда sql — только на чтение и в пределах прав вашей роли (см. Таблицы для запросов sql). Qubix.db работает с вашей базой с правами того пользователя базы, который назван в строке соединения, — и читает, и пишет.
Где доступна команда:
- в скриптах раздела Скрипты — и по расписанию, и по кнопке Запустить;
- в обработчиках страниц сайта — на каждом заходе посетителя и по кнопке Запуск в панели Тестовый запуск (см. Бэкенд сайта).
В правилах Britva Qubix.db нет.
Подключиться можно только к узлу, который администратор вписал в перечень Узлы баз данных для скриптов (Настройки Qubix → JavaScript). Пока перечень пуст, подключения к базам выключены. Подробнее — в разделе Куда можно подключаться.
Как это выглядит
function main() {
const db = Qubix.db.connect('postgres://report:[email protected]:5432/crm')
const rows = db.query('SELECT id, email FROM customers WHERE country = $1 LIMIT 10', ['DE'])
for (const row of rows) console.log(row.id, row.email)
const changed = db.exec('UPDATE customers SET checked_at = now() WHERE country = $1', ['DE'])
console.log('изменено строк:', changed)
db.close()
}
Все вызовы синхронные: результат приходит сразу, как у sql и ctx.fetch, поэтому await не нужен. С MySQL всё так же, только строка начинается с mysql://, а на месте значения в тексте запроса стоит ?.
Строка соединения
Qubix.db.connect(...) принимает строку такого вида:
postgres://user:password@host:5432/database?sslmode=require
mysql://user:password@host:3306/database?tls=true
| Часть | Что значит |
|---|---|
| схема | postgres:// или postgresql:// — PostgreSQL; mysql:// или mariadb:// — MySQL и MariaDB |
user:password@ | пользователь базы и его пароль. Что запросам можно, решают права этого пользователя в вашей базе |
host | один узел — имя или адрес. Несколько узлов через запятую не принимаются, путь к файлу сокета вместо узла тоже: подключение идёт только по сети |
:port | необязательно; без порта — 5432 у PostgreSQL и 3306 у MySQL |
/database | имя базы |
?… | параметры — только из таблицы ниже |
Строка соединения читается по правилам адреса, поэтому знаки вроде #, ?, /, % или пробела в имени пользователя и в пароле записывайте в процентной записи. Проще всего собрать строку через encodeURIComponent:
const url = 'postgres://report:' + encodeURIComponent(password) + '@db.example.com:5432/crm'
Параметры строки:
| Параметр | База | Значения | Если не указан |
|---|---|---|---|
sslmode | PostgreSQL | disable, allow, prefer, require, verify-ca, verify-full | prefer: шифрованный канал, если база его поддерживает, иначе открытый |
channel_binding | PostgreSQL | disable, prefer, require | prefer |
require_auth | PostgreSQL | scram-sha-256 — пароль уходит только по SCRAM: сервер, который просит его иначе, получает отказ | как попросит сервер |
application_name | PostgreSQL | любое имя — под ним соединение видно в списке соединений базы | — |
tls | MySQL, MariaDB | true — шифрование с проверкой сертификата; skip-verify — шифрование без проверки сертификата; preferred — шифрование без проверки, если сервер его поддерживает, иначе открытый канал; false — без шифрования | без шифрования |
Если администратор направил выход сервера в интернет через Tor (вкладка Исходящие запросы), база в интернете подключается, только если соединение проверяет сервер базы по имени: для PostgreSQL добавьте sslmode=verify-full, для MySQL и MariaDB — tls=true. Выход Tor — чужой сервер: без проверки он получил бы пароль базы. sslmode=verify-ca не хватает — без своего корневого сертификата он принимает любой общий сертификат, не сверяя имя, — как и channel_binding=require с require_auth=scram-sha-256: пароль тогда не идёт открытым, но выход, сам принявший шифрование, получает достаточно, чтобы подобрать слабый пароль вне сети. База во внутренней сети из перечня узлов подключается напрямую, и правило к ней не относится.
Другие параметры не принимаются — отказ перечисляет, какие поддерживаются. Параметры, которые заставили бы подключение читать файлы на сервере Qubix (passfile, sslkey, sslcert, sslrootcert, service, servicefile у PostgreSQL и allowAllFiles у MySQL), отвергаются всегда.
Строка без :// считается именем сохранённого подключения, а сохранённых подключений нет: такой вызов получает отказ. Передавайте строку соединения.
Запросы: query и exec
Qubix.db.connect(...) возвращает объект соединения:
| Метод | Что делает | Что возвращает |
|---|---|---|
query(text, params) | выполняет запрос, который отдаёт строки | массив строк; каждая строка — объект, ключи которого — имена столбцов |
exec(text, params) | выполняет изменяющий оператор: INSERT, UPDATE, DELETE и другие | число строк, которые оператор изменил |
close() | закрывает соединение | ничего |
Значения передаются отдельно от текста запроса — вторым аргументом, массивом, а в тексте на их месте стоят $1, $2… у PostgreSQL и ? у MySQL. Значение не вклеивается в текст запроса, поэтому экранировать его вручную не нужно и подменить запрос через него нельзя. Второй аргумент можно не передавать, если значений нет.
| Значение в JavaScript | Как уходит в базу |
|---|---|
строка, число, true / false, null | как есть |
Date | дата и время |
ArrayBuffer | двоичные данные |
| массив, объект | не принимается — отказ. Для столбца JSON передайте JSON.stringify(значение) |
Что приходит в строках ответа:
| Столбец в базе | Значение в строке ответа |
|---|---|
| целое | число. Целое за пределами точного целого JavaScript (Number.MAX_SAFE_INTEGER) приходит строкой, чтобы не потерять цифры |
дробное (real, double precision, FLOAT, DOUBLE) | число |
десятичное (numeric, DECIMAL) | строка — так не теряется точность |
| JSON | строка — разберите её через JSON.parse |
дата (DATE) | строка вида 2026-09-27 |
| дата и время | строка в формате ISO в UTC, например 2026-09-27T10:00:00Z |
двоичное (bytea, BLOB) | ArrayBuffer |
логическое (boolean в PostgreSQL) | true / false. В MySQL BOOLEAN — это целое TINYINT, поэтому приходит число 0 или 1 |
NULL | null |
| прочее | строка |
У столбцов даты и времени без часового пояса (timestamp в PostgreSQL, DATETIME в MySQL) пояса в самом значении нет: время приходит таким, каким записано, с приписанной Z.
Столбцы с одинаковыми именами — например, id из двух таблиц — дают в строке одно свойство, и остаётся последнее. Давайте им разные имена через AS.
Закрытие соединения
close() закрывает соединение. Повторный вызов ничего не ломает, а запрос по закрытому соединению получает отказ. Соединение, которое вы не закрыли, закроется само: у скрипта — сразу после прогона, у обработчика страницы — до ответа посетителю. Закрывайте соединение, как только оно больше не нужно, — удобнее всего в блоке finally.
Все запросы одного соединения идут в одном сеансе базы: посреди прогона соединение не подменяется другим. Закрытие не возвращает место в пределе подключений — в счёт идёт каждая попытка подключиться к узлу за прогон.
Пример: сводка из статистики Qubix в свою базу
Скрипт считает депозиты за сутки по странам командой sql и записывает итог в вашу таблицу PostgreSQL одной вставкой.
function main() {
const rows = sql`
SELECT geo, countIf(event = 'dep') AS deps
FROM qubix_events
WHERE event_time > now() - INTERVAL 1 DAY
AND geo != ''
GROUP BY geo`
if (!rows.length) return
// Одна вставка на все строки: каждый вызов query и exec идёт в счёт предела запросов.
const values = []
const params = []
for (const r of rows) {
values.push('(now(), $' + (params.length + 1) + ', $' + (params.length + 2) + ')')
params.push(r.geo, r.deps)
}
const db = Qubix.db.connect('postgres://writer:[email protected]:5432/reports')
try {
const inserted = db.exec('INSERT INTO daily_deps (taken_at, country, deps) VALUES ' + values.join(', '), params)
console.log('записано строк:', inserted)
} finally {
db.close()
}
}
Для MySQL замените $1, $2… на ?. Большой объём разбивайте на несколько вставок, но не на вставку для каждой строки: упрётесь в предел запросов.
Куда можно подключаться
Подключиться можно только к узлу из перечня Узлы баз данных для скриптов. Перечень ведёт администратор на вкладке JavaScript окна Настройки Qubix — о самой вкладке рассказано в статье Система. Перечень один на скрипты и обработчики страниц сайтов; пока он пуст, подключения к базам выключены.
Записи пишутся по одной на строку:
| Запись | Что открывает |
|---|---|
имя, например db.example.com | этот узел — но только если имя ведёт на адрес в интернете |
адрес, например 10.0.0.5, или сеть, например 10.0.0.0/24 | этот адрес или эту сеть, в том числе во внутренней сети; после этого до базы можно достучаться и по имени, которое ведёт на этот адрес |
* | любой адрес в интернете, но не внутреннюю сеть |
Базу во внутренней сети — вашей или самого сервера Qubix — вписывайте адресом или сетью: одного имени для неё мало. Запись, которую не удалось прочитать (например, сеть с неверной маской), пропускается, и о ней пишется строка в журнал сервера. Правка перечня действует со следующего прогона, перезапуск не нужен.
Пределы
Пределы задаёт администратор на той же вкладке JavaScript. У скриптов они стоят под перечнем узлов и считаются на один прогон. У обработчиков страниц сайтов пределы свои — в блоке Обработчики страниц сайтов: подключения и запросы там считаются на один запрос страницы, а строки, размер ответа и срок подписаны так же, как у скриптов.
| Предел | Что ограничивает | Что происходит сверх него |
|---|---|---|
| Базы данных: макс. подключений на запуск (у обработчиков — Базы данных: макс. подключений на запрос страницы) | число подключений: в счёт идёт каждая попытка подключиться к узлу, в том числе неудачная; вызов, отвергнутый раньше — при пустом перечне узлов или перечне без единой читаемой записи, при неверной строке соединения, имени вместо неё или значении не строкой, — в счёт не идёт | отказ |
| Базы данных: макс. запросов на запуск (у обработчиков — Базы данных: макс. запросов на запрос страницы) | число вызовов query и exec по всем соединениям вместе | отказ |
| Базы данных: макс. строк на запрос | число строк в ответе одного query | лишние строки не читаются: база останавливает запрос, query возвращает первые строки, а в журнал прогона уходит предупреждение |
| Базы данных: макс. размер ответа, байт | объём ответа базы на один запрос. Считаются все присланные байты вместе со служебными, поэтому предел наступает раньше, чем его достигнет сумма самих значений | отказ, и соединение закрывается |
| Базы данных: срок запроса, мс | время одного запроса и одного подключения | отказ. Соединение PostgreSQL после него остаётся рабочим, соединение MySQL закрывается |
Кроме этих пределов, запросы останавливает общий срок: время прогона скрипта или Макс. время работы обработчика, мс у обработчика страницы. Правка пределов действует со следующего прогона.
Отказы и что с ними делать
Отказ — обычная ошибка JavaScript. Её можно перехватить try…catch и продолжить работу. Неперехваченная ошибка останавливает прогон скрипта с ошибкой, а обработчик страницы отвечает посетителю пустым ответом 500; чтобы увидеть причину, повторите запрос в панели Тестовый запуск (см. Бэкенд сайта).
Тексты отказов — по-английски. В таблицах ниже опущено общее начало Qubix.db: , с которого начинается большинство из них. Перечень узлов и пределы названы в текстах так, как они подписаны в английском интерфейсе; вместо N в тексте стоит число.
Подключение и перечень узлов
| Отказ | Что случилось | Что делать |
|---|---|---|
connections to databases are off: the "Database hosts for scripts" list (System → JavaScript) is empty | перечень узлов пуст | попросить администратора вписать узел базы |
connections to databases are off: … has no entry that can be read (see the server log) | в перечне нет ни одной записи, которую удалось прочитать | администратору — исправить записи; непрочитанные названы в журнале сервера |
host "db.example.com" is not in the "Database hosts for scripts" list (System → JavaScript) | узла нет в перечне | вписать узел: имя — для базы в интернете, адрес или сеть — для базы во внутренней сети |
host "db.example.com" is on an internal network: its address or network must be in the "Database hosts for scripts" list (System → JavaScript) | имя есть в перечне, но ведёт на внутренний адрес | вписать в перечень адрес или сеть базы |
host "db.example.com" has no address, host "db.example.com" could not be resolved | у имени из перечня не нашлось адреса | проверить имя узла |
Qubix.db.connect("crm"): saved connections are not available yet; … | передано имя, а не строка соединения | передать строку соединения |
database type "redis" is not supported: use postgres:// or mysql:// | незнакомая схема | начать строку с postgres://, postgresql://, mysql:// или mariadb:// |
one host per connection, the connection string names no host, port "…" is not a number from 1 to 65535, cannot read the connection string: … | строка соединения собрана неверно | проверить строку; служебные знаки в пароле записать в процентной записи |
parameter "foo" is not supported (supported: …), sslmode="bogus" is not supported (supported: …), parameter "…" is given more than once | параметр не из перечня, неверное значение или повтор | оставить параметры из таблицы выше, каждый по одному разу |
this server reaches the internet through Tor, and a Tor exit reads a connection that does not check the database server: add … | сервер ходит в интернет через Tor, а строка соединения не проверяет сервер базы | добавить sslmode=verify-full для PostgreSQL, tls=true для MySQL — см. выше |
parameter "sslrootcert" is not allowed: it makes the driver read files on the server | параметр заставил бы подключение читать файлы сервера Qubix | убрать параметр |
host "/var/run/postgresql" is a unix socket; only TCP connections are allowed, parameter "host" is not allowed: … | вместо узла в адресе — путь к сокету или узел в параметрах | указать узел и порт в самом адресе |
Пределы и срок
У обработчика страницы вместо per run в тексте стоит per page request.
| Текст | Что случилось | Что делать |
|---|---|---|
connection cap exceeded: N per run (limit "Databases: max connections per run") | исчерпан предел подключений | открыть одно соединение и вести через него все запросы |
query cap exceeded: N per run (limit "Databases: max queries per run") | исчерпан предел запросов | объединять запросы: много строк — одной вставкой, много чтений — одним запросом |
the answer is larger than N bytes (limit "Databases: max response size, bytes") | ответ больше предела; соединение закрыто | выбирать меньше столбцов и строк; для следующих запросов открыть новое соединение |
the database sent more than N bytes while connecting (limit "Databases: max response size, bytes") | база прислала слишком много уже при входе | проверить, что по этому адресу и порту отвечает именно база данных и что предел объёма ответа не слишком мал |
query stopped after N ms (limit "Databases: query timeout, ms") | запрос или подключение не уложились в срок | ускорить запрос или попросить администратора поднять предел |
this connection was closed after an answer exceeded the response cap, this connection was closed after a query ran out of time | соединение закрыто после одного из двух отказов выше | открыть новое соединение |
the run is out of time; the query was stopped | кончилось время прогона скрипта или обработчика | сократить работу, которую делает один прогон |
result truncated to N rows (limit "Databases: max rows per query") | это не отказ, а предупреждение в журнале прогона: строк больше предела, пришли первые | сузить выборку: условие WHERE, LIMIT, свёртка в запросе |
Значения и вызовы
| Текст | Что случилось |
|---|---|
this connection is closed | запрос по соединению, которое уже закрыто через close() |
parameter #2 is an array; pass a string, number, boolean, null, Date or ArrayBuffer | значение — массив |
parameter #2 is an object; pass JSON.stringify(value) for a JSON column | значение — объект |
query(text, params): params must be an array, e.g. [42, "text"] | значения переданы не массивом |
Qubix.db.connect(connection): pass a connection string such as postgres://user:password@host:5432/database | в Qubix.db.connect передана не строка |
Ответы самой базы
Всё остальное — ответ вашей базы: неверный пароль, нет такой таблицы, ошибка в тексте запроса. Он идёт после Qubix.db: вместе с пояснениями, которые добавляет подключение: например, при неверном пароле у PostgreSQL текст содержит failed SASL auth: FATAL: password authentication failed for user "report". Пароль из строки соединения в текст не попадает, а адреса, на которые ведёт узел, заменены его именем.
Скрипт и обработчик страницы сайта: в чём разница
| Скрипт | Обработчик страницы сайта | |
|---|---|---|
| Когда исполняется | по расписанию и по кнопке Запустить | на каждом заходе посетителя и по кнопке Запуск в панели Тестовый запуск |
| Пределы | считаются на прогон; строки под перечнем узлов | считаются на запрос страницы; блок Обработчики страниц сайтов |
| Отказ по пределу | … per run | … per page request |
| Предупреждение об обрезке строк | в консоли прогона | только в консоли тестового запуска: журнал живого захода нигде не показывается |
| Общий срок | время прогона скрипта | Макс. время работы обработчика, мс |
| Незакрытые соединения | закрываются сразу после прогона | закрываются до ответа посетителю |
| Повторное использование соединений | нет: каждый прогон открывает свои соединения | нет: каждый заход открывает свои соединения, и вход в базу повторяется при каждом показе страницы |
| Неперехваченный отказ | прогон завершается ошибкой | посетитель получает пустой ответ 500 |
| Посетитель ушёл, не дождавшись ответа | — | обработчик прерывается, и идущий запрос к базе снимается: изменение, которое он вносил, может как примениться, так и нет |
Своё имя Qubix в корне кода | сохранение отказывает: имя занято платформой | обработчик работает, но Qubix.db ему недоступен |
Если обработчик не вернулся даже после своего срока, посетитель получает ответ 503, а соединения этого прогона закрываются, когда обработчик всё-таки завершится.
Каждый показ страницы заново входит в базу, поэтому держите запросы обработчика короткими и не открывайте больше одного соединения там, где хватит одного.
И кнопка Запустить у скрипта, и кнопка Запуск у обработчика выполняют настоящие запросы к вашей базе: изменения через exec применяются сразу. Проверяйте запись на тестовой таблице или тестовой базе.