Цели
Один объект запроса, шесть способов превратить его во что-то ещё. Каждая цель объявляет, чего не
умеет выразить, и бросает QueryRenderException вместо приближения.
Querio.Sql
Параметризованный SQL для трёх движков. Значения не попадают в текст.
var result = SqlRenderer.Render(spec, schema, PostgreSqlDialect.Instance);
result.Sql; // сам оператор
result.Parameters; // пары имя/значение для вашего клиента
| SQL Server | PostgreSQL | SQLite | |
|---|---|---|---|
| Ограничение строк | TOP (n) либо OFFSET/FETCH при пропуске |
LIMIT/OFFSET |
LIMIT/OFFSET |
| Поиск без учёта регистра | LIKE |
ILIKE |
LIKE |
| Усечение до дня | DATEADD(day, DATEDIFF(day, 0, x), 0) |
date_trunc('day', x) |
date(x) |
| Перцентиль | только без группировки | да | нет |
| Табличные функции | да | да | нет |
| Усечение до квартала | да | да | нет |
Две детали, которые стоит знать:
- Постраничность без сортировки.
OFFSETнужно, в чём пропускать, поэтому SQL Server получаетORDER BY (SELECT NULL), когда у запроса нет своей сортировки. - Перцентиль с группировкой в SQL Server. Там
PERCENTILE_CONT- оконная функция, а не агрегат, и её нельзя совместить сGROUP BYв одном операторе. Диалект отказывается и объясняет, а не выдаёт то, что вернёт другую форму результата.
Встроенные функции пишутся без кавычек (UPPER(x)), квалифицированное имя - это процедура и
квотируется по частям ([dbo].[CalcTax](x)). Закавыченная встроенная отправила бы движок искать
несуществующую процедуру.
Querio.OneC
Язык запросов 1С - цель, которая держит всю модель честной, потому что она не SQL-образная.
var result = OneCRenderer.Render(spec, schema);
result.Query; // кириллические ключевые слова
result.Parameters; // в стиле &p0
Не умеет: перцентили, OFFSET, перекрёстные соединения, табличные функции. Каждое ограничение
объявлено, а не обнаруживается опытным путём.
1С вообще не умеет заключать идентификатор в кавычки, поэтому имя, не являющееся допустимым идентификатором 1С, отвергается, а не экранируется. Это честный ответ: экранировать нечем.
Относительные окна разрешаются в параметр при отрисовке, поскольку у 1С нет аналога DATEADD. Сам
запрос по-прежнему хранит смещение; закрепляет его только эта цель.
Querio.Linq
Настоящие деревья выражений, а не интерпретатор.
// Отдать EF Core то, что он умеет транслировать
var predicate = QueryPredicate.For<RequestRow>(spec, schema);
var rows = dbContext.Requests.Where(predicate).ToList();
// Либо выполнить весь запрос по объектам
var result = QueryExecutor.Execute(spec, schema, new QuerySources()
.Add("requests", requests)
.Add("apiKeys", apiKeys));
Зависимости на Entity Framework нет и не нужно. System.Linq.Expressions входит в базовую
библиотеку классов, а EF принимает эти деревья напрямую.
Деревья состоят из настоящих операторов: фиксированное значение сужается до CLR-типа члена ещё при
сборке, поэтому условие выходит как x.DurationMs > 100 с типизированной константой, а принадлежность
множеству - одним узлом List<T>.Contains, а не цепочкой сравнений. Провайдер читает это целиком.
Не умеет: правое и полное внешние соединения. У последовательности объектов нет для них естественной формы.
Агрегат по нулю строк даёт null, а не ноль, - как и хранилище.
У функций, объявленных схемой, нет реализации, пока вы её не дадите:
var functions = new QueryFunctionLibrary()
.Register<string, string>("upper", text => text.ToUpperInvariant())
.RegisterTable("activeUsers", args => users.Where(u => u.LastSeen > (DateTime)args[0]!));
Вызов нереализованной функции падает. Схема говорит, что функция существует, но никогда - что она делает.
Querio.Http
Строка запроса, в обе стороны.
var text = QueryHttp.Render(spec, schema); // читаемая: хранить, логировать, показывать
var url = QueryHttp.ToUri(spec, schema, "/reports"); // percent-encoded: класть в ссылку
var back = QueryHttp.ParseUri(url, schema);
from=requests:r
&select=r.route as route,count() as total
&where=r.timestamp ge -30d and r.error eq 'true'
&groupby=r.route
&orderby=total desc
&top=20
Ключи логические, поэтому тот же текст означает тот же запрос и в иначе названном хранилище. Значения пишутся ровно в той форме, в какой хранятся, поэтому чтение возвращает то же значение, а не похожее на вид: кавычки, запятые и скобки внутри значения переживают дорогу.
Querio.Text
Запрос как фраза, в обе стороны.
QueryDescriber.Describe(spec, schema);
// From Requests (r), showing Route and the number of rows called total,
// where (Timestamp is at least the last 30 days and Error is "true"),
// grouped by Route, ordered by total descending, first 20
Написано подписями, которые выбрал человек, а не именами хранилища. Все связки сменные, поэтому запрос можно описать - и прочитать обратно - на другом языке:
var labels = QueryDescriptionLabels.Default with { From = "из", Showing = "показываем", Where = "где" };
var said = QueryDescriber.Describe(spec, schema, labels);
var read = QueryDescriber.Parse(said, schema, labels);
Поля уточняются псевдонимом только когда запрос достаёт больше одного источника, поэтому однотабличный случай (а отчёт обычно такой) остаётся коротким. Чтение применяет то же правило, так что разойтись они не могут.
Querio.Language
SQL-образный текст с тем единственным, чего SQL не умеет. См. язык запросов.
Что выбрать
- Хранить запрос там, где человек может его прочесть или поправить ->
Querio.HttpилиQuerio.Text - Показать запрос на утверждение ->
Querio.Text - Дать кому-то написать запрос ->
Querio.Language - Выполнить в базе ->
Querio.SqlилиQuerio.OneC - Выполнить по объектам или через EF ->
Querio.Linq
Они сочетаются: описать запрос словами для окна подтверждения, отрисовать в SQL для выполнения, а ссылку положить в журнал аудита. Каждый раз - один и тот же объект.