Table of Contents

Язык запросов

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);

Нужна, чтобы открыть в текстовом редакторе запрос, собранный где-то ещё - в коде или в конструкторе. Соединения выписываются полностью, включая те, что получились из пути по внешнему ключу, - по причине выше.