Язык запросов 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