Язык запросов FunQL строится на основе диалекта SQL базы данных PostgreSQL (точнее, только DQL-части, иными словами - поддерживается только команда SELECT). Поверх этого добавляются:
Документация предполагает что для ознакомления с основами языка запросов вы пользуетесь документацией PostgreSQL.
Язык находится в разработке, поэтому многие конструкции стандартного SQL ещё не поддерживаются. Мы постепенно добавляем поддержку новых конструкций; если вам нужен какой-то оператор или конструкция которую мы ещё не поддерживаем, сообщите нам об этом!
string)Например 'Hello!'.
Формат совпадает с расширенным форматом строк в PostgreSQL (префикс E), другими словами -- поддерживаются escape-выражения наподобие C.
int)Например 42.
Используется 32-битная точность.
decimal)Например 3.14.
Внимание. Несмотря на то что в базе данных числа хранятся в формате произвольной точности, внутри FunDB в настоящий момент для их преобразования используются 128-битные числа. Это всё равно обеспечивает достаточную точность для финансовых операций. В FunApp же для отображения чисел используется стандартный тип данных number в JavaScript, что обеспечивает лишь 64-битную точность. Если подобной точности вам недостаточно, сообщите нам - в будущем планируется перейти на "честные" числа с произвольной точностью в нашем API и в веб-приложении.
Поддерживается функция округления до ближайшего целого или до N десятичных знаков round().
bool)true или false. Регистр символов не важен, как в стандартном SQL.
date)Задаются через оператор конверсии типов, например '2019-01-01' :: date. Не зависят от временной зоны.
datetime)Задаётся через оператор конверсии типов, например '2019-01-01 08:00' :: datetime. В запросах задаётся всегда в UTC.
interval)Интервал времени, также задаётся через оператор конверсии типов: например '12 days' :: interval. Поддерживаются все форматы задания, поддерживаемые PostgreSQL.
array(item_type))Используется синтаксис массивов PostgreSQL, например array [1, 2, 3]. Поддерживают только одну степень глубины (т.е. вложенные массивы запрещены). Тип массива также можно задавать через оператор конверсии типов, напр. array [] :: array(int).
json)Обладают выделенным специальным синтаксисом, позволяющим задавать части массива через выражения. Например: { "foo": 1 + 2 } или [ 1 + 2, 'foo' ]. Внутри можно использовать произвольные выражения, включая значения из ячеек таблиц.
uuid)Используется для хранения идентификаторов формата UUID.
reference(entity_name))Отношения к другим сущностям. Представляют из себя числовые идентификаторы id из записей заданной сущности. Сущность задаётся идентификатором.
enum(val1, val2...))Значение с фиксированными строковыми вариантами. Варианты задаются как строки-аргументы типа. Для колонок и аргументов этого типа выполняются соответствующие проверки. Значения брабатываются и отображается в заданном порядке.
В ближайших планах
Файл будет отдельным типом записи.
Общий синтаксис:
[ { arg_name :: type [ NULL ] [ DEFAULT value ] [ @{ ... } ], ... }: ]
SELECT ...
[ FOR INSERT INTO entity_name ]
Пример запроса:
{
$id reference(usr.orders)
}:
SELECT
-- Как отображать результат
@"type" = 'form',
-- Ширины блоков формы в сетке шириной в 12 ячеек
-- Блоки, которые не вмещаются переносятся на следующую строку
@"block_sizes" = array[
7, 5,
12
],
number @{
-- Индекс блока из параметра block_sizes
"form_block" = 1
},
order_datetime @{
"form_block" = 0
},
client @{
"form_block" = 0
},
status @{
"form_block" = 1
},
-- Вложенное представление
{
ref: &usr.goods_for_order_table_conn,
args: {
id: $id
}
} as goods_in_order @{
"control" = 'UserView',
"form_block" = 2,
"caption" = 'Goods In Order'
}
FROM
usr.orders
WHERE
id = $id
FOR INSERT INTO
usr.orders
Следуют стандартному синтаксису SQL, напр. "schemas" или просто schemas.
Вместо указания названия колонки можно указывать __main, что означает "главная колонка". Для подробностей о главной колонке см. FunDB.
Обозначают внешние значения, которые передаются в запрос.
{
$my_argument int,
$my_second_argument string
}:
Для названий аргументов используется стандартный синтаксис идентификаторов в SQL. Внутри запроса на аргументы можно ссылаться как $arg_name, например $id.
Для аргументов можно задавать значения по умолчанию через конструкцию DEFAULT <значение>:
{
$my_defaulted_argument int DEFAULT 0
}:
Аргументы могут быть необязательными (опция NULL):
{
$my_optional_argument int NULL
}:
Аргументы можно динамически изменять с помощью редактора аргументов.
Используются только для изменения отображения аргументов в редакторе аргументов.
У аргументов могут быть атрибуты, они похожи на атрибуты ячеек, но в них нельзя использовать динамические значения.
{
$my_argument int @{ caption: 'мой аргумент' }
}:
Также существуют глобальные аргументы, которые всегда передаются в запрос. На глобальные аргументы ссылаются через $$global_name.
Их список:
| Аргумент | Описание |
|---|---|
$$lang |
текущий язык пользователя в формате BCP 47, например 'ru-RU' |
$$user |
текущее имя пользователя (его e-mail) |
$$user_id |
текущий id пользователя в таблице "public"."users". Может быть NULL |
$$transaction_time |
время начала текущей транзакции |
Аргумент может быть опциональным если задан с ключевым словом NULL. В этом случае он необязателен для выполнения запроса; если аргумент отсутствует, он будет равен NULL.
Здесь SELECT ... это SELECT из DQL со стандартными конструкциями и дополнениями. В настоящий момент не все конструкции из PostgreSQL реализованы. Текущий синтаксис:
SELECT [ select_expression ] [, ...]
[ FROM from_item [, ...] ]
[ WHERE condition ]
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ]
[ GROUP BY grouping_element [, ...] ]
[ ORDER BY expression [ ASC | DESC ] [, ...] ]
[ LIMIT count ]
[ OFFSET start ]
, где select_expression это одно из:
@ attribute_name = expression
expression [ AS output_name ] [ @{ [ attribute_name = expression ] [, ...] } ]
, from_item это одно из:
table_name
( select ) AS alias
( values ) AS alias ( column_alias [, ...] )
from_item join_type from_item ON join_condition
, join_type это одно из:
[ INNER ] JOIN
LEFT JOIN
RIGHT JOIN
, values это:
VALUES ( expression [, ...] ) [, ...]
В FunQL нельзя указать схему по умолчанию, т.е. для всех таблиц необходимо явно указывать схему, например public.entities.
Атрибуты позволяют вам указывать параметры для отображения запроса. Например вы можете указать ширину колонки для отображения в таблице. Атрибуты бывают нескольких видов. Подробную информацию об поддерживаемых в FunApp атрибутах смотрите для каждого вида отображения.
Задаются как @name = value. Применяются ко всей строке. Если в значении не используются идентификаторы ячеек то атрибут применяется ко всему запросу (и называются атрибутами запроса).
Задаются для конкретной ячейки как column_name @{ name = value }. Если в значении не используются идентификаторы ячеек то атрибут применяется к колонке (и называется атрибутом колонки).
В случае когда запрашивается только информация о представлении (например, при создании новых записей в FunApp) недоступны никакие значения из реальных сущностей и никакие аргументы. Соответственно атрибуты строк и ячеек, в которых используются любые идентификаторы, не могут быть вычислены и не возвращаются. Атрибуты, которые заданы простыми выражениями, в том числе без упоминания аргументов представления, называются чистыми - такие атрибуты вычисляются всегда. Из-за этого отображение представления в FunApp может различаться для новых и для существующих записей. Например:
@"type" = 'form'
будет работать в любом режиме, а:
@"type" = $type
не будет работать в FunApp при создании новой записи.
В FunQL отслеживаются источники выбранных колонок, что позволяет редактировать их значения прямо из результата запроса в FunApp. Для этого не нужно ничего делать дополнительно - любые упоминания колонок без дополнительных выражений над ними, например SELECT "name" AS "user_view_name" FROM "public"."user_views", позволяют редактировать ячейки (в данном случае имя отображения). Любые операции над колонкой, напр. "foo" + 0, делают её неотслеживаемой - такие колонки нельзя редактировать. Также нельзя редактировать вычислимые колонки, подробности смотрите в структуре таблиц FunDB.
Задаётся через FOR INSERT INTO entity_name. Если главная сущность указана, проверяется что в запросе можно выделить указанную сущность и все её обязательные колонки. При этом в составе результата запроса возвращается дополнительная информация о выбранных колонках, а в FunApp становится возможным создавать новые записи в таблице прямо из результата данного запроса.
Сущность можно выделить если:
FROM;LEFT JOIN, или правым в RIGHT JOIN;Поведение этих операторов совпадает с поведением в PostgreSQL за некоторыми исключениями.
NOT;AND;OR;|| (конкатенация строк);=;!= и <>;LIKE и NOT LIKE (~~ и !~~);<, <=, > >=;+, -, *, /;IN (значения, ...) и NOT IN (значения, ...);IN (подзапрос) и NOT IN (подзапрос);IS NULL, IS NOT NULL;IS DISTINCT FROM, IS NOT DISTINCT FROM;CASE;COALESCE;:: (конверсия типов). При этом не поддерживается конверсия скаляров в массивы и наоборот;-> и ->> (получение элементов JSON-объектов и массивов).Поддерживаются следующие агрегатные функции:
sum;avg;min;max;count;bool_and.Подробную документацию по ним вы можете прочитать на сайте PostgreSQL. Если вам нужны функции, которые мы ещё не добавили - напишите нам!
Следующие функции разрешены и их поведение соответствует поведению в PostgreSQL:
| Функция | Тип результата | Описание | Пример | Результат |
|---|---|---|---|---|
abs(x) |
тип аргумента | модуль числа | abs(-17.4) |
17.4 |
round(decimal) |
тип аргумента | округление до ближайшего целого | round(42.4) |
42 |
round(v decimal, s integer) |
decimal |
округление v до s десятичных знаков |
round(42.4382, 2) |
42.44 |
| Функция | Тип результата | Описание | Пример | Результат |
|---|---|---|---|---|
substr(string, from [, count]) |
string |
Извлекает подстроку | substr('alphabet', 3, 2) |
ph |
split_part(string, delimiter, position) |
string |
Возвращает N-ую подстроку из строки с разделителем | split_part('one, two, three', ',' 2) |
two |
| Функция | Тип результата | Описание | Пример | Результат |
|---|---|---|---|---|
age(datetime) |
interval |
Вычитает дату/время из current_date (полночь текущего дня) | age('1957-06-13'::datetime) |
43 years 8 mons 3 days |
date_part(text, datetime) |
decimal |
Возвращает поле даты | date_part('day', '2001-02-16'::datetime) |
16 |
date_part(text, interval) |
decimal |
Возвращает поле даты | date_part('month', '2 years 3 months')::interval |
3 |
date_trunc(text, datetime) |
datetime |
Отсекает компоненты даты до заданной точности | date_trunc('hour', '2001-02-16 20:38:40'::datetime) |
2001-02-16 20:00:00 |
date_trunc(text, interval) |
interval |
Отсекает компоненты даты до заданной точности | date_trunc('hour', '2 days 3 hours 40 minutes'::interval) |
2 days 03:00:00 |
isfinite(date) |
bool |
Проверяет конечность даты (её отличие от +/-бесконечности) | isfinite('2001-02-16'::date) |
true |
isfinite(datetime) |
bool |
Проверяет конечность времени (его отличие от +/-бесконечности) | isfinite('2001-02-16 21:28:30'::datetime) |
true |
isfinite(interval) |
bool |
Проверяет конечность интервала | isfinite('4 hours'::interval) |
true |
| Функция | Тип результата | Описание | Пример |
|---|---|---|---|
to_char(datetime, text) |
string |
преобразует время в текст | to_char('2020-05-01 15:35:00'::datetime, 'HH12:MI:SS') |
to_char(interval, text) |
string |
преобразует интервал в текст | to_char('15h 2m 12s'::interval, 'HH24:MI:SS') |
to_char(integer, text) |
string |
преобразует целое в текст | to_char(125, '999') |
to_char(decimal, text) |
string |
преобразует плавающее одинарной/двойной точности в текст | to_char(125.8, '999D9') |
Если вам нужны функции, которые мы ещё не добавили - напишите нам!
Задаются как &user_view_name (или &schema.user_view_name). Используются в частности атрибутах для ссылок на другие отображения. Такие ссылки проверяются на верность, т.е. нельзя сослаться на несуществующее отображение. Если для отображения не указана схема, используется схема в которой находится данное отображение. Ссылка &"public"."events" эквивалентна JSON-объекту { "schema": 'public', "name": 'events' }.
=>Оператор разыменования отношений. Может использоваться для получения данных произвольной глубины, напр.:
SELECT
column_fields.entity_id=>schema_id=>name
FROM
column_fields
В настоящий момент такие выражения запрещены в выражениях ON для JOIN.
Используйте этот функционал с осторожностью, чтобы избежать утечки данных.
Таблицы в запросе можно помечать как привилегированные, например: FROM base.contacts WITH SUPERUSER ROLE. Это позволяет пропустить ограничение видимых записей в таблице согласно правам доступа.
Оператор ! помечает колонку, как привилегированную. К таким колонкам также не применяются права доступа, то есть их значения не скрываются. Оператор можно комбинировать с оператором =>, чтобы ко всей присоединённой таблице не применялись ограничения. Например, в выражении foo!=>bar значение bar будет видно всем пользователям. Если bar является полем-связью, то чтобы избежать фильтрации при выводе главного поля, нужно использовать ! на bar: foo!=>bar!.
Этот функционал полезен в случае, если ограничения доступа приводят к медлительности запросов, и администратор уверен, что в конкретном случае ими можно принебречь (например, в агрегирующих запросах).
INHERITED FROM и OFTYPEДанные выражения позволяют проверить тип конкретной записи. "contacts"."sub_entity" INHERITED FROM "prganizations" вернёт TRUE если запись относится к типу "organizations" или любому типу, унаследованному от него. "contacts"."sub_entity" TYPEOF "organizations вернёт TRUE только если запись относится к самому типу "organizations". Использование оператора OFTYPE в общем случае не рекомендуется; используйте его только если уверены что вам нужно точно определять тип выражения! INHERITED FROM позволяет писать выражения, которые продолжат работать при добавлении новых типов в иерархию наследования.
После проверок на тип становятся доступны колонки, которые существуют только в наследной таблице. Например, если колонка first_name существует только в наследнике people таблицы contacts, то выражение SELECT CASE WHEN "contacts"."sub_entity" INHERITED FROM "people" THEN "contacts"."first_name" ELSE 'Нет имени' END FROM "base"."contacts" сработает, хотя в таблице contacts колонка first_name отсутствует.
.@ и .@@ для получения значений атрибутов Оператор .@ извлекает значение атрибута из поля. Оператор .@@ извлекает значение атрибута из сущности (таблицы).
{
$enum_arg enum('foo', 'bar') null @{
caption = 'Тест',
text = mapping
when 'foo' then 'Foo'
when 'bar' then 'Bar'
end,
attr1 = mapping
when 'foo' then 42
when 'bar' then 73
else 1337
end,
}
}:
SELECT
/* .@text returns 'Foo ' or 'Bar ' */
@title = $enum_arg.@text || ' демо',
/* $enum_arg value
-> returns 'foo' or 'bar' or NULL */
$enum_arg as enum_arg,
/* attr1 attribute value
-> returns 42 or 73 or NULL */
$enum_arg.@attr1 AS enum_arg_attr @{
test2 = mapping
when 42 then 'cool'
when 73 then 'cooler'
end
},
/* returns cell_variant value for a specific action status */
action=>stage.@cell_variant AS stage_attr @{
cell_variant = action=>stage.@cell_variant,
},
/* stage caption value stored in "actions.stage" default attributes */
stage.@caption AS caption_attr,
/* foo attribute value from the joined table "actions" */
actions.@@foo AS foo_attr
FROM pm.actions_for_contacts
LEFT JOIN (SELECT @foo = 123, id, stage
FROM pm.actions) AS actions
ON actions_for_contacts.action = actions.id