Язык запросов
Querio.Language читает запрос, написанный текстом. Он похож на SQL, потому что SQL людям уже
знаком, - и он не SQL, в чём и весь смысл.
select [r].[route], [r].[apiKeyId].[name] as [key], count(*) as [total]
from [dbo].[RequestLog] as [r]
where [r].[timestamp] >= now - 30 day and [r].[error] = true
group by [r].[route], [r].[apiKeyId].[name]
order by [total] desc
limit 20
Проход по внешнему ключу
Разница - в [r].[apiKeyId].[name]. Так не умеет ни один SQL: каждое имя, кроме последнего, - это
ключ, по которому надо пройти.
var spec = QueryLanguage.Parse("select [r].[apiKeyId].[ownerId].[name] from [requests] as [r]", schema);
// spec.Joins -> apiKeys, а следом users, цепляющийся за него
Цепочки идут на любую глубину. Важные правила:
- Каждый переход становится соединением, а цель отрисовывает его как ей удобно: явным
JOINв SQL, точечной ссылкой в 1С. - Соединения внешние, намеренно. Проход по пустому ключу не должен убирать строку, иначе запрос тихо отвечает на более узкий вопрос, чем задан.
- Один и тот же переход, написанный дважды, даёт одно соединение. Упомяните
[r].[apiKeyId].[name]трижды - соединение всё равно будет одно. - Переход именуется либо полем-ключом, либо самой связью. Имя связи - единственный способ пройти составной ключ, у которого нет одного поля, на которое можно указать.
Сахар тратится при чтении: нигде не записано, что соединение пришло точкой, поэтому обратная запись покажет соединения. Тот же запрос, другие символы.
Имена
Имена сущностей и полей принимаются и логические, и физические:
from [dbo].[RequestLog] as [r] -- то, что видно в базе
from [requests] as [r] -- то, как это называет схема
В обоих случаях в spec попадает логическое имя, поэтому текст остаётся переносимым. Скобки
необязательны вокруг всего, что не ключевое слово; ]] - это литеральная ], как в T-SQL.
Грамматика
select [distinct] <элемент> [, <элемент>]... | *
from <сущность|функция(...)> [as] <псевдоним>
[<вид>] join <сущность|функция(...)> [as] <псевдоним> (through <связь> | on <a.x> = <b.y> [and ...])
where <условие>
group by <значение> [, <значение>]...
having <условие>
order by <значение|имя-вывода> [asc|desc] [, ...]
limit <n> offset <n>
| Агрегаты | count(*), count(distinct x), sum(x), avg(x), min(x), max(x), percentile(x, 0.95) |
| Периоды | trunc(x, day) - также minute, hour, week, month, quarter, year |
| Относительное время | now - 30 day, now + 2 week |
| Сравнение | =, <>, !=, >, >=, <, <= |
| Текст | contains, startswith, endswith |
| Множества и диапазоны | in (...), not in (...), between a and b, not between a and b |
| Пустота | is null, is not null |
| Виды соединений | inner, left, right, full, cross |
Соединение без through и без on выводится, когда ровно одна объявленная связь достаёт до того,
что уже есть в запросе. Если кандидатов два - об этом будет сказано, а не угадано.
Чтение сломанного текста
Набираемый текст сломан почти всегда, поэтому читатель никогда не останавливается на первой ошибке:
var result = QueryLanguage.Read(text, schema);
result.Spec; // то, что удалось собрать, - часто не null даже при ошибках
result.Diagnostics; // все проблемы, каждая со Start, Length, Message, Severity
result.IsValid;
Каждая диагностика несёт точный участок, который она покрывает, - именно его подчёркивает редактор. Частичный spec не менее важен: по нему подсказки продолжают понимать контекст, пока остаток строки ещё бессмыслен.
QueryLanguage.Parse - строгая дверь: запрос или исключение, - для вызывающих, которым нужно одно из
двух.
Подсказки
var candidates = QueryCompletion.Suggest(text, caret, schema);
Каждый кандидат несёт, что вставить, что показать, какого он рода и какой участок заменяет. Точка после псевдонима предлагает и достигнутые поля, и ключи, по которым можно пройти глубже:
id field Id
route field Маршрут
apiKeyId field Ключ API
apiKeyId navigation -> Ключи API
timestamp field Момент
Передайте IQueryCapabilities, чтобы сузить ответы под цель, - так же, как это делает
QueryChoices.
Обратная запись
var text = QueryLanguage.Write(spec, schema);
Нужна, чтобы открыть в текстовом редакторе запрос, собранный где-то ещё - в коде или в конструкторе. Соединения выписываются полностью, включая те, что получились из пути по внешнему ключу, - по причине выше.