1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
# Лексическая структура
<!-- markdownlint-disable blanks-around-fences -->
Запрос на языке YQL представляет собой валидный UTF-8 текст, который состоит из *команд* (statement) разделенных символом точка с запятой (`;`).
Последняя точка с запятой при этом может отсутствовать.
Каждая команда, в свою очередь, состоит из последовательности *токенов* допустимых для данной команды.
Токеном может быть *ключевое слово*, *идентификатор*, *литерал* и другие.
Токены разделяются пробельными символами (пробел, табуляция, перевод строки) либо *комментариями*. Комментарий не является частью команды и синтаксически эквивалентен пробельному символу.
## Режимы совместимости синтаксиса {#lexer-modes}
Поддерживаются два режима совместимости синтаксиса:
* Расширенный C++ (по умолчанию)
* ANSI SQL
Режим ANSI SQL включается с помощью специального комментария `--!ansi_lexer`, который должен стоять в начале запроса.
Особенности интерпретации лексических элементов в разных режимах совместимости описаны ниже.
## Комментарии {#comments}
Поддерживаются следующие виды комментариев:
* Однострочные: начинается с последовательности символов `--` (два минуса *подряд*) и продолжается до конца строки
* Многострочные: начинается с последовательности символов `/*` и заканчивается на последовательности символов `*/`
```yql
SELECT 1; -- A single-line comment
/*
Some multi-line comment
*/
```
В режиме совместимости синтаксиса C++ (по умолчанию) многострочный комментарий заканчивается на *ближайшей* последовательности символов `*/`.
В режиме совместимости синтаксиса ANSI SQL учитывается вложенность многострочных комментариев:
```yql
--!ansi_lexer
SELECT * FROM T; /* комментарий /* вложенный комментарий, без ansi_lexer будет ошибка */ */
```
## Ключевые слова и идентификаторы {#keywords-and-ids}
**Ключевые слова** – это токены, имеющее фиксированное значение в языке YQL. Примеры ключевых слов – `SELECT`, `INSERT`, `FROM`, `ACTION` и т.д. Ключевые слова регистронезависимы, то есть `SELECT` и `SeLEcT` эквивалентны.
Список ключевых слов не фиксирован – по мере развития языка он будет расширяться. Ключевое слово не может содержать цифры и начинаться или заканчиваться символом подчеркивания.
**Идентификаторы** – это токены, которые идентифицируют имена таблиц, колонок и других объектов в YQL. Идентификаторы в YQL всегда регистрозависимы.
Идентификатор может быть записан в теле программы без специального оформления, если он:
* Не является ключевым словом
* Начинается с латинской буквы или подчеркивания
* Последующими символами могут быть латинская буква, подчеркивание или цифра
```yql
SELECT my_column FROM my_table; -- my_column and my_table are identifiers
```
Для записи в теле запроса произвольного идентификатора он заключается в обратные кавычки (бэктики):
```yql
SELECT `column with space` from T;
SELECT * FROM `my_dir/my_table`
```
Идентификатор в обратных кавычках никогда не интерпретируется как ключевое слово:
```yql
SELECT `select` FROM T; -- select - имя колонки в таблице T
```
При использовании обратных кавычек применим стандартный C-эскейпинг:
```yql
SELECT 1 as `column with\n newline, \x0a newline and \` backtick `;
```
В режиме совместимости синтаксиса ANSI SQL произвольные идентификаторы также могут быть выделены заключением их в двойные кавычки. Для включения двойной кавычки в идентификатор в кавычках она должна быть удвоена:
```yql
--!ansi_lexer
SELECT 1 as "column with "" double quoute"; -- имя колонки будет: column with " double quoute
```
## SQL хинты {#sql-hints}
SQL хинты – это специальные настройки, которые позволяют пользователю влиять на план выполнения запроса
(например, включать/выключать определенные оптимизации, форсировать стратегию JOIN-а и т.п.).
В отличие от [PRAGMA](pragma.md), SQL хинты обладают локальным действием – они привязаны к определенной точке YQL запроса (обычно следуют после ключевого слова)
и влияют только на соответствующий statement или даже его часть.
SQL хинты представляют собой набор настроек "имя-список значений" и задаются внутри комментариев специального вида –
первым символом комментария с SQL хинтами должен быть `+`:
```yql
--+ Name1(Value1 Value2 Value3) Name2(Value4) ...
```
Имя SQL хинта должно состоять из алфавтно-цифровых ASCII символов и начинаться с буквы. Регистр букв в имени хинта игнорируется.
После имени хинта в скобках задается произвольное количество значений, разделенных пробелами. В качестве значения может выступать произвольный набор символов.
Если в наборе символов значения имеется пробел или скобка, то необходимо использовать одинарные кавычки:
```yql
--+ foo('value with space and paren)')
```
```yql
--+ foo('value1' value2)
-- эквивалетно
--+ foo(value1 value2)
```
Одинарную кавычку внутри значения необходимо эскейпить путем дублирования:
```yql
--+ foo('value with single quote '' inside')
```
Неизвестные имена SQL хинтов (либо синтаксически некорректные хинты) никогда не вызывают ошибок – они просто игнорируется:
```yql
--+ foo(value1) bar(value2 baz(value3)
-- из-за пропущенной закрывающей скобки в bar эквивалетно
--+ foo(value1)
```
Такое поведение связано с нежеланием ломать написанные ранее валидные YQL запросы с комментариями, которые похожи на хинты.
При этом синтаксически корректные SQL хинты в неожиданном для YQL месте вызывают предупреждение:
```yql
-- в данный момент хинты после SELECT не поддерживаются
SELECT /*+ foo(123) */ 1; -- предупреждение 'Hint foo will not be used'
```
Хочется заметить, что SQL хинты – это именно подсказки оптимизатору, поэтому:
* хинты никогда не влияют на результат запроса
* по мере развития оптимизаторов в YQL вполне возможна ситуация, в которой хинт становится неактуальным и начнет игнорироваться (например, полностью поменялся алгоритм, который настраивался данным хинтом, либо оптимизатор настолько улучшился, что гарантированно выбирает оптимальное решение, поэтому какие-то ручные настройки будут скорее вредить)
## Строковые литералы {#string-literals}
Строковый литерал (константа) записывается как последовательность символов, заключенных в одинарные кавычки. Внутри строкового литерала можно использовать правила эскейпинга в стиле C:
```yql
SELECT 'string with\n newline, \x0a newline and \' backtick ';
```
В режиме совместимости синтаксиса С++ (по-умолчанию) разрешается использовать вместо одинарных кавычек двойные:
```yql
SELECT "string with\n newline, \x0a newline and \" backtick ";
```
В режиме совместимости синтаксиса ASNI SQL двойные кавычки используются для идентификаторов, а единственный вид эскепинга который действует для строковых литералов – это дублирование символа одиночной кавычки:
```yql
--!ansi_lexer
SELECT 'string with '' quote'; -- результат: string with ' quote
```
На основании строковых литералов могут быть получены [литералы простых типов](../builtins/basic#data-type-literals).
### Многострочные строковые литералы {#multiline-string-literals}
Многострочный строковой литерал записывается в виде произвольного набора символов между двойными собачками `@@`:
```yql
$text = @@some
multiline
text@@;
SELECT LENGTH($text);
```
Если необходимо вставить в текст двойную собачку, ее необходимо удвоить:
```yql
$text = @@some
multiline with double at: @@@@
text@@;
SELECT $text;
```
### Типизированные строковые литералы {#typed-string-literals}
* Для строкового литерала, включая [многострочный](#multiline-string-literals), по умолчанию используется тип `String` (см. также [PRAGMA UnicodeLiterals](pragma.md#UnicodeLiterals)).
* С помощью следующих суффиксов можно явно управлять типом литерала:
* `s` — `String`;
* `u` — `Utf8`;
* `y` — `Yson`;
* `j` — `Json`.
#### Пример
```yql
SELECT "foo"u, '[1;2]'y, @@{"a":null}@@j;
```
## Числовые литералы {#literal-numbers}
* Целочисленные литералы по умолчанию имеют тип `Int32`, если попадают в его диапазон, и в противном случае автоматически расширяются до `Int64`.
* С помощью следующих суффиксов можно явно управлять типом литерала:
* `l` — `Int64`;
* `s` — `Int16`;
* `t` — `Int8`.
* Добавление суффикса `u` превращает тип в соответствующий беззнаковый:
* `ul` — `Uint64`;
* `u` — `Uint32`;
* `us` — `Uint16`;
* `ut` — `Uint8`.
* Также для целочисленных литералов доступна запись в шестнадцатеричной, восьмеричной и двоичной форме с помощью префиксов `0x`, `0o` и `0b`, соответственно. Их можно произвольным образом комбинировать с описанными выше суффиксами.
* Литералы с плавающей точкой по умолчанию имеют тип `Double`, но с помощью суффикса `f` его можно сузить до `Float`.
```yql
SELECT
123l AS `Int64`,
0b01u AS `Uint32`,
0xfful AS `Uint64`,
0o7ut AS `Uint8`,
456s AS `Int16`,
1.2345f AS `Float`;
```
## Литералы PostgreSQL {#pgliterals}
Строковые и числовые литералы Pg типов можно создавать с помощью специальных суффиксов (аналогично простым [строковым](#string-literals) и [числовым](#literal-numbers) литералам).
### Целочисленные литералы {#intliterals}
Суффикс | Тип | Комментарий
----- | ----- | -----
`p` | `PgInt4` | 32-битное знаковое целое (в PostgreSQL нет беззнаковых типов)
`ps`| `PgInt2` | 16-битное знаковое целое
`pi`| `PgInt4` |
`pb`| `PgInt8` | 64-битное знаковое цело
`pn`| `PgNumeric` | знаковое целое произвольной точности (до 131072 цифр)
### Литералы с плавающей точкой {#floatliterals}
Суффикс | Тип | Комментарий
----- | ----- | -----
`p` | `PgFloat8` | число с плавающей точкой (64 бит double)
`pf4`| `PgFloat4` | число с плавающей точкой (32 бит float)
`pf8`| `PgFloat8` |
`pn` | `PgNumeric` | число с плавающей точкой произвольной точности (до 131072 цифр перед запятой, до 16383 цифр после запятой)
### Строковые литералы {#stringliterals}
Суффикс | Тип | Комментарий
----- | ----- | -----
`p` | `PgText` | текстовая строка
`pt`| `PgText` |
`pv`| `PgVarchar` | текстовая строка
`pb`| `PgBytea` | бинарная строка
{% note warning "Внимание" %}
Значения строковых/числовых литералов (т.е. то что идет перед суффиксом) должны быть валидной строкой/числом с точки зрения YQL.
В частности, должны соблюдаться правила эскейпинга YQL, а не PostgreSQL.
{% endnote %}
Пример:
```yql
SELECT
1234p, -- pgint4
0x123pb, -- pgint8
"тест"pt, -- pgtext
123e-1000pn; -- pgnumeric
;
```
### Литерал массива
Для построения литерала массива используется функция `PgArray`:
```yql
SELECT
PgArray(1p, NULL ,2p) -- {1,NULL,2}, тип _int4
;
```
### Конструктор литералов произвольного типа {#literals_constructor}
Литералы всех типов (в том числе и Pg типов) могут создаваться с помощью конструктора литералов со следующей сигнатурой:
`Имя_типа(<строковая константа>)`.
Напрмер:
```yql
DECLARE $foo AS String;
SELECT
PgInt4("1234"), -- то же что и 1234p
PgInt4(1234), -- в качестве аргумента можно использовать литеральные константы
PgInt4($foo), -- и declare параметры
PgBool(true),
PgInt8(1234),
PgDate("1932-01-07"),
;
```
Также поддерживается встроенная функция `PgConst` со следующей сигнатурой: `PgConst(<строковое значение>, <тип>)`.
Такой способ более удобен для кодогенерации.
Например:
```yql
SELECT
PgConst("1234", PgInt4), -- то же что и 1234p
PgConst("true", PgBool)
;
```
Для некоторых типов в функции `PgConst` можно указать дополнительные модификаторы. Возможные модификаторы для типа `pginterval` перечислены в [документации PostgreSQL](https://www.postgresql.org/docs/16/datatype-datetime.html).
```yql
SELECT
PgConst(90, pginterval, "day"), -- 90 days
PgConst(13.45, pgnumeric, 10, 1); -- 13.5
;
```
|