Описание
Начало работы
Использование
Политика версионирования
- Основная версия увеличивается при изменениях API
- Дополнительная версия увеличивается при обратно совместимых изменениях SQL
- Номер патча увеличивается при изменениях только в бинарном файле
- Версия библиотеки (определяемая
PG_MODULE_MAGICв PostgreSQL 18 и выше) включает полную семантическую версию, которая видна в выводе функцииpgch_version()или функции Postgrespg_get_loaded_modules(). - Версия расширения (определяемая в control-файле) включает только основную
и дополнительную версии, которые видны в таблице
pg_catalog.pg_extension, в выводе функцииpg_available_extension_versions()и в\dx pg_clickhouse.
v0.1.0 до v0.1.1, приносит пользу всем базам данных, в которых загружена v0.1, и
не требует выполнения ALTER EXTENSION, чтобы воспользоваться обновлением.
С другой стороны, релиз, в котором увеличивается дополнительная или основная версия,
будет сопровождаться SQL-скриптами обновления, и все существующие базы данных, содержащие
расширение, должны выполнить ALTER EXTENSION pg_clickhouse UPDATE, чтобы воспользоваться
обновлением.
Справочник по SQL DDL
CREATE EXTENSION
WITH SCHEMA, чтобы установить расширение в определённую схему (рекомендуется):
ALTER EXTENSION
-
После установки нового релиза pg_clickhouse используйте предложение
UPDATE: -
Используйте
SET SCHEMA, чтобы переместить расширение в новую схему:
DROP EXTENSION
CASCADE, чтобы удалить и их:
CREATE SERVER
driver: драйвер подключения к ClickHouse: “binary” или “http”. Обязательный параметр.compression: сжатие native-протокола для драйвера “binary”: одно из значений “none”, “lz4” или “zstd”. По умолчанию — “lz4”. Для драйвера “http” не используется.dbname: база данных ClickHouse, используемая при подключении. По умолчанию — “default”.host: имя хоста сервера ClickHouse. По умолчанию — “localhost”;port: порт сервера ClickHouse, к которому нужно подключаться. Значения по умолчанию следующие:- 9440, если
driver— “binary” иhost— хост ClickHouse Cloud - 9004, если
driver— “binary” иhostне является хостом ClickHouse Cloud - 8443, если
driver— “http” иhost— хост ClickHouse Cloud - 8123, если
driver— “http” иhostне является хостом ClickHouse Cloud
- 9440, если
min_tls_version: минимальная версия протокола TLS, согласуемая при подключениях, использующих TLS. Одно из значений:TLSv1,TLSv1.1,TLSv1.2илиTLSv1.3. По умолчанию — минимальная версия, заданная в самой библиотеке TLS. Применяется к обоим драйверам.secure: управляет использованием TLS для подключения. Возможные значения:auto(по умолчанию): использовать TLS, еслиhost— хост ClickHouse Cloud илиport— защищенный порт; в противном случае — plaintext.on(илиtrue/yes/1): всегда использовать TLS. Значениеportпо умолчанию — 8443 (“http”) или 9440 (“binary”).off(илиfalse/no/0): никогда не использовать TLS. Значениеportпо умолчанию — 8123 (“http”) или 9000 (“binary”).
ALTER SERVER
DROP SERVER
CASCADE, чтобы
также удалить эти зависимости:
CREATE USER MAPPING
taxi_srv:
user: Имя пользователя ClickHouse. По умолчанию — “default”.password: Пароль пользователя ClickHouse.
ALTER USER MAPPING
DROP USER MAPPING
IMPORT FOREIGN SCHEMA
LIMIT TO, чтобы импортировать только указанные таблицы:
EXCEPT, чтобы исключить таблицы:
CREATE FOREIGN TABLE
database: Имя удалённой базы данных. По умолчанию используется база данных, заданная для внешнего сервера.table_name: Имя удалённой таблицы. По умолчанию используется имя, указанное для внешней таблицы.engine: [движок таблицы], используемый таблицей ClickHouse. ДляCollapsingMergeTree()иAggregatingMergeTree()pg_clickhouse автоматически применяет параметры к функциональным выражениям, выполняемым для таблицы.
-
column_name: Имя столбца на стороне ClickHouse, которое используется вместо имени атрибута PostgreSQL при генерации запросов и операций вставки. Это полезно для сопоставления имён столбцов PostgreSQL в нижнем регистре без кавычек со столбцами ClickHouse, чувствительными к регистру, например: -
AggregateFunction: Имя агрегатной функции, применяемой к столбцу типа AggregateFunction Type. Сопоставьте тип данных с типом ClickHouse, передаваемым в функцию, и укажите имя агрегатной функции через соответствующую опцию столбца; pg_clickhouse автоматически добавитMergeк агрегатной функции, вычисляющей этот столбец. -
SimpleAggregateFunction: Имя агрегатной функции, применяемой к столбцу типа SimpleAggregateFunction Type. Сопоставьте тип данных с типом ClickHouse, передаваемым в функцию, и укажите имя агрегатной функции через соответствующую опцию столбца.
ALTER FOREIGN TABLE
DROP FOREIGN TABLE
CASCADE:
Справочник по DML SQL
EXPLAIN
VERBOSE приводит к выводу запроса ClickHouse “Remote SQL”:
SELECT
nodes и выполняем JOIN с ней вместо удалённой таблицы:
node_id вместо локального столбца, а затем выполнить JOIN
с таблицей lookup:
node_id, сокращая
число строк, которые нужно вернуть в Postgres, с 1000 (то есть
всех) до всего 8 — по одной на каждый узел.
Партиционированные таблицы
enable_partitionwise_aggregate PostgreSQL вычисляет частичный
агрегат под Append, а затем завершающий агрегат над ним объединяет эти
частичные результаты. pg_clickhouse передаёт частичный агрегат внешней партиции
в ClickHouse:
Когда выполняется проталкивание частичных агрегатов
- Декомпозируемые агрегаты, у которых переходное состояние уже является итоговым
значением, проталкиваются напрямую:
count,sum,min,max,bool_and/every,bool_or,bit_and,bit_orиbit_xor. avgдля целых чисел проталкивает состояние{count, sum}в виде массива.avg,var_pop,var_samp,stddev_popиstddev_sampдля чисел с плавающей запятой проталкивают состояние{N, sum, sum of squared deviations}в виде массива.
FILTER (WHERE …) проталкивается вместе с этими агрегатными функциями.
Когда используется локальная обработка
internal,
не имеют переносимого представления, поэтому внешняя партиция вместо этого загружает свои строки
и выполняет агрегацию локально. Это относится ко всем агрегатам для numeric, а также к
avg(bigint) и avg(interval). Агрегаты DISTINCT, упорядоченных множеств и вариативные
агрегаты также используют локальную обработку.
PREPARE, EXECUTE, DEALLOCATE
{param:type}:
параметры:
INSERT
COPY
⚠️ Ограничения Batch API В pg_clickhouse пока не реализована поддержка Batch API вставки PostgreSQL FDW. Поэтому COPY сейчас использует операторы INSERT для вставки записей. Это будет исправлено в одном из будущих релизов.
LOAD
SET
pg_clickhouse.session_settings
pg_clickhouse.session_settings задаёт [настройки ClickHouse],
которые будут применяться к последующим запросам. Пример:
join_use_nulls для внешних JOIN и transform_null_in для семейства IN
(см. IN и семантика NULL).
date_time_output_format: HTTP-драйвер требует, чтобы значение было “iso”format_tsv_null_representation: HTTP-драйвер требует значение по умолчаниюoutput_format_tsv_crlf_end_of_lineHTTP-драйвер требует значение по умолчанию
pg_clickhouse.session_settings; для этого либо используйте [предварительную загрузку разделяемой библиотеки], либо
просто воспользуйтесь одним из объектов в расширении, чтобы гарантировать его загрузку.
pg_clickhouse.pushdown_regex
pg_clickhouse.pushdown_regex определяет, будет ли pg_clickhouse
выполнять pushdown функций и операторов регулярных выражений. По умолчанию это включено;
установите для этого параметра значение false, чтобы отключить pushdown для них:
ALTER ROLE
SET оператора ALTER ROLE для предварительной загрузки pg_clickhouse
и/или для установки его параметров для определённых ролей:
RESET оператора ALTER ROLE, чтобы сбросить предварительную загрузку pg_clickhouse
и/или его параметры:
Предварительная загрузка
session_preload_libraries
Типы данных
Любой столбец также можно прочитать как
text, varchar или другой строковый тип. Значение
сначала преобразуется в указанный выше тип PostgreSQL, а затем выводится с помощью функции
вывода этого типа. Для значений UInt64, превышающих максимум bigint, по-прежнему возникает ошибка, поэтому выводите их
с помощью функции ClickHouse toString().
Ниже приведены дополнительные примечания и подробности.
BYTEA
SELECT выведет:
Справочник по Function и операторам
Функции
clickhouse_raw_query
host=localhost port=8123. Поддерживаются следующие параметры подключения:
driver: Используемый драйвер подключения: “http” или “binary”; по умолчанию “http”host: Хост, к которому нужно подключиться; обязателен.port: Порт, к которому нужно подключиться. По умолчанию8123для драйвера “http” или9000для драйвера “binary”; при подключении к хосту ClickHouse Cloud используются соответственно8443или9440dbname: Имя базы данных, к которой нужно подключиться.username: Имя пользователя, от которого выполняется подключение; по умолчаниюdefaultpassword: Пароль, используемый для аутентификации; по умолчанию пароль отсутствует
\N), но представление
отдельных значений различается: драйвер “http” возвращает собственное TSV-форматирование ClickHouse
без изменений, тогда как драйвер “binary” преобразует каждое значение с помощью
функции вывода PostgreSQL.
По умолчанию ни одна роль не имеет доступа EXECUTE к этой функции; рассмотрите возможность
предоставления доступа GRANT только тем ролям, которым действительно требуется выполнять
произвольные запросы ClickHouse, например выделенной роли администратора ClickHouse:
Полезно для запросов, которые не возвращают записей, но запросы, которые все же возвращают значения,
будут возвращены как одно текстовое значение:
clickhouse_server_version
major.minor.patch для указанного
стороннего сервера, при необходимости подключаясь с использованием параметров сервера и
пользовательского сопоставления текущего пользователя:
SELECT version() и кэширует её на весь срок
жизни соединения.
clickhouse_query
driver сервера,
учётные данные, базу данных и кэш подключений.
Первый аргумент — имя сервера, созданного с помощью CREATE SERVER. Требуется
список определений столбцов (AS name(col type, ...)): PostgreSQL необходимо
знать структуру результата до получения строк, и она должна соответствовать столбцам, которые возвращает запрос. Значения преобразуются из ClickHouse в объявленные типы так же,
как для столбца внешней таблицы. Операторам, не возвращающим результаты, например DDL,
нечего объявлять; вместо этого выполняйте их с помощью
clickhouse_perform.
По умолчанию ни одна роль не имеет права EXECUTE; предоставьте его роли, чтобы разрешить ей использовать
функцию.
clickhouse_perform
clickhouse_query не имеет формы результата, которую можно объявить. Он
разрешает сервер так же, как clickhouse_query, повторно используя его driver,
учётные данные, базу данных и кэш подключений.
Как процедуру его следует вызывать с помощью CALL, а не SELECT; он не возвращает
строк. По умолчанию ни у одной роли нет права EXECUTE; предоставьте его роли с помощью GRANT, чтобы она могла
использовать процедуру.
Функции с pushdown
HAVING и WHERE). Для них используются следующие
эквиваленты в ClickHouse:
abs: absfactorial: factorialmod(int2/int4/int8/numeric): остаток от деленияpow&power(float8/numeric): powround: roundsin,cos,tan,atan,atan2,sinh,cosh,tanh,asinh,degrees,radians,pi: математические функции ClickHouse с такими же именами. Дляasin,acos,atanh,acoshpushdown не применяется: PG выдаёт ошибку для входных данных вне диапазона, тогда как CH возвращаетNaN.date_part:date_part('day'): toDayOfMonthdate_part('doy'): toDayOfYeardate_part('dow'): toDayOfWeekdate_part('year'): toYeardate_part('month'): toMonthdate_part('hour'): toHourdate_part('minute'): toMinutedate_part('second'): toSeconddate_part('quarter'): toQuarterdate_part('isoyear'): toISOYeardate_part('week'): toISOYeardate_part('epoch'): toISOYear
date_trunc:date_trunc('week'): toMondaydate_trunc('second'): toStartOfSeconddate_trunc('minute'): toStartOfMinutedate_trunc('hour'): toStartOfHourdate_trunc('day'): toStartOfDaydate_trunc('month'): toStartOfMonthdate_trunc('quarter'): toStartOfQuarterdate_trunc('year'): toStartOfYear
extract(field FROM source): такие же соответствия, как уdate_partdate(timestamp)&date(timestamptz): toDate (при обратном преобразовании как алиас CHdate)array_position: indexOf с nullIf для преобразования0вNULLи arraySlice при наличии третьего аргумента, задающего начальный индекс поиска; обратите внимание, чтоnanпока не сопоставляетсяarray_cat: arrayConcatarray_append: arrayPushBackarray_prepend: arrayPushFrontarray_remove: arrayRemovecardinality: длинаarray_length(array, 1):nullIf(length(array), 0)array_length&cardinality: длинаarray_to_string: arrayStringConcatstring_to_array: splitByStringsplit_part: splitByString + индексация массиваtrim_array: arrayResizearray_fill: arrayWithConstantarray_reverse: arrayReversearray_shuffle: arrayShufflearray_sample: arrayRandomSamplearray_sort: arraySort / arrayReverseSortbtrim: trimBothltrim: trimLeftrtrim: trimRightconcat_ws: concatWithSeparatorlower(text): lowerUTF8upper(text): upperUTF8substring(text, ...)&substr(text, ...): substringUTF8substring(bytea, ...)&substr(bytea, ...): substringlength(text): lengthUTF8length(bytea)&octet_length: lengthreverse(text): reverseUTF8reverse(bytea): reversestrpos: positionUTF8regexp_like: matchregexp_match: extractGroups если регулярное выражение содержит подвыражения в скобках; в противном случае extractAll со срезом через arraySlice.regexp_replace: replaceRegexpOne или replaceRegexpOne при наличии флагаgregexp_split_to_array: splitByRegexpmd5: MD5encode(bytea, fmt), еслиfmt— строковая константа (регистронезависимая):encode(bytea, 'hex'): hex обёрнутая в lower, поскольку PostgreSQL выводит hex в нижнем регистре.encode(bytea, 'base64'): base64Encode обёрнутая в replaceRegexpAll для воспроизведения разрыва строки MIME (RFC 2045), вставляемого PostgreSQL через каждые 76 символов.encode(bytea, 'base64url')(PostgreSQL 19+): base64URLEncode, которая соответствует URL-алфавиту RFC 4648 PostgreSQL без дополнения.
json_extract_path_text: синтаксис подстолбцовjson_extract_path: toJSONString + синтаксис подстолбцовjsonb_extract_path_text: синтаксис подстолбцовjsonb_extract_path: toJSONString + синтаксис обращения к подстолбцамbit_count(bytea): bitCountto_timestamp(float8): toDateTime64to_char(timestamp[tz], fmt): formatDateTime еслиfmt— это строковая константа, для каждого ключевого слова которой есть точный эквивалент в ClickHouse. Поддерживаемые ключевые слова см. в to_char() в разделе «Примечания по совместимости». В противном случае функция выполняется локально в PostgreSQL.statement_timestamp,transaction_timestamp, &clock_timestamp: nowInBlock64 (nowInBlock64(9, $session_timezone))CURRENT_DATE: now и toDate (toDate(now($session_timezone)))now,CURRENT_TIMESTAMP, &LOCALTIMESTAMP: now64 (now64(9, $session_timezone))CURRENT_TIMESTAMP(n)&LOCALTIMESTAMP(n): now64 (now64(n, $session_timezone))CURRENT_DATABASE: Передаётся в качестве значения из функции PostgreSQL.CURRENT_SCHEMA: Передаётся как значение из функции PostgreSQL.CURRENT_CATALOG: Передаётся как значение из функции PostgreSQL.CURRENT_USER: Передаётся в качестве значения из функции PostgreSQL.USER: передаётся в качестве значения из функции PostgreSQL.CURRENT_ROLE: Передаётся как значение из функции PostgreSQL.SESSION_USER: Передаётся как значение, возвращаемое функцией PostgreSQL.
Операторы pushdown
- Срез массива (
arr[L:U]): arraySlice @>(массив содержит): hasAll<@(массив содержится в): hasAll&&(массивы пересекаются): hasAny~(совпадение с регулярным выражением): match!~(нет совпадения с регулярным выражением): match~*(регистронезависимое отсутствие совпадения с регулярным выражением): match!~*(регистронезависимое отсутствие совпадения с регулярным выражением): match->>(извлечение элемента JSON/JSONB как текста): синтаксис подстолбцов->(извлечение JSON/JSONB): toJSONString + синтаксис подстолбцов
Семантика IN и NULL
IN в рамках двузначной логики: если проверяемое значение не находит
совпадений, возвращается 0, даже если участвует NULL, тогда как PostgreSQL возвращает
NULL. Чтобы сохранить семантику PostgreSQL, pg_clickhouse безусловно выполняет pushdown семейства
IN для списка констант или массива (IN, NOT IN, = ANY, = ALL,
<> ANY, <> ALL): нативной или недорогой формы, когда можно
доказать, что ни проверяемое значение, ни элемент массива не могут быть NULL, либо защищённой
формы CASE в остальных случаях, которая вместо этого проверяет NULL во время выполнения,
вычисляя точный трёхзначный результат PostgreSQL (TRUE, FALSE, NULL) в
любом контексте, включая позиции значений, например в списке SELECT или
GROUP BY.
Фильтр NOT IN (SELECT ...) по столбцам с типом Nullable также поддерживает pushdown;
при обратном преобразовании добавляются компенсирующие проверки, сохраняющие поведение PostgreSQL:
множество, содержащее NULL, исключает каждую строку, а NULL в проверяемом значении проходит только
при сравнении с пустым множеством. Каждая проверка опускается, если объявление NOT NULL
доказывает её ненужность. В отличие от форм с массивами выше, эта проверка применяется только
в простом условии фильтрации (или под NOT); IN (SELECT ...) (в позиции значения) и тела
сгруппированных или агрегированных подзапросов по-прежнему не поддерживают pushdown.
Объявление столбцов как NOT NULL максимально расширяет возможности pushdown, позволяя вместо этого
отправлять более дешёвую форму без проверок; IMPORT FOREIGN SCHEMA делает это автоматически для
столбцов ClickHouse без типа Nullable. При доказательстве учитываются константы, отличные от NULL,
столбцы NOT NULL и базовая арифметика (+, -, *, унарный -) над ними.
Эти правила предполагают, что в ClickHouse используется значение по умолчанию transform_null_in = 0, которое
pg_clickhouse задаёт для каждого запроса через значение по умолчанию
параметра pg_clickhouse.session_settings,
чтобы профиль сервера ClickHouse не мог незаметно его изменить. Установка
transform_null_in = 1 нарушает семантику каждого IN, для которого выполняется pushdown.
Пользовательские функции
Pushdown для расширений
re2
@~→ matchre2match→ matchre2extract→ extractre2extractall→ extractAllre2regexpextract→ regexpExtractre2extractgroups→ extractGroupsre2replaceregexpone→ replaceRegexpOnere2replaceregexpall→ replaceRegexpAllre2countmatches→ countMatchesre2countmatchescaseinsensitive→ countMatchesCaseInsensitivere2multimatchany→ multiMatchAnyre2multimatchanyindex→ multiMatchAnyIndexre2multimatchallindices→ multiMatchAllIndices
intarray
idx→ indexOf
fuzzystrmatch
soundex: soundexlevenshtein(с двумя аргументами): editDistanceUTF8
Приведения типов с pushdown
CAST(x AS bigint), если
типы данных совместимы. Для несовместимых типов pushdown завершится ошибкой; если x в этом
примере имеет тип ClickHouse UInt64, ClickHouse откажется приводить это значение.
Чтобы выполнять pushdown приведений к несовместимым типам данных, pg_clickhouse предоставляет
следующие функции. Они вызывают исключение в PostgreSQL, если pushdown не выполняется.
Агрегатные функции с pushdown
- any_value
- array_agg
- avg
- bit_and
- bit_or
- bit_xor
- bool_and / every
- bool_or
- count
- corr
- covarpop
- covarsamp
- min
- max
- stddev_pop
- stddev_samp / stddev
- string_agg
- sum
- var_op
- var_samp /variance
Пользовательские агрегаты
Pushdown для агрегатных функций ordered set
ORDER BY — в качестве аргументов. Например, следующий запрос PostgreSQL:
ORDER BY — DESC и NULLS FIRST —
не поддерживаются и вызовут ошибку.
percentile_cont(double): quantilepercentile_cont(double[]): quantilespercentile_disc(double): quantileExactLowpercentile_disc(double[]): quantilesExactLow
Пользовательская агрегатная функция ordered set
quantile(double): quantilequantileExact(double): quantileExact
Пользовательские агрегатные функции ordered set
Эти пользовательские [агрегатные функции ordered set], созданные pg_clickhouse, поддерживают pushdown внешних запросов для отдельных параметрических [агрегатных функций] ClickHouse. Если любую из этих функций нельзя выполнить с pushdown, будет вызвано исключение.Оконные функции с pushdown
OVER (PARTITION BY ... ORDER BY ...), включая спецификации рамки окна, где это
применимо.
- row_number
- rank
- dense_rank
- ntile
- cume_dist
- percent_rank
- lead
- lag
- first_value
- last_value
- nth_value
min/max(с секциейOVER)
row_number, rank, dense_rank, ntile, cume_dist,
percent_rank) не включают секцию рамки окна, поскольку ClickHouse
не поддерживает спецификации рамки окна для этих функций.
Примечания по совместимости
Регулярные выражения
-
PostgreSQL поддерживает [регулярные выражения POSIX], а ClickHouse —
регулярные выражения RE2. Учитывайте различия в их поведении: используйте RE2,
если регулярное выражение будет обрабатываться в ClickHouse (например, в
предложении
WHERE), и POSIX, если оно будет обрабатываться в Postgres (например, в предложенииSELECT). -
pg_clickhouse проталкивает [флаги Postgres], добавляя их в начало
регулярного выражения ClickHouse внутри
(?). Например:Преобразуется в -
Единственные флаги, которые поддерживаются в обоих вариантах и потому могут использоваться при обработке в
ClickHouse:
RE2 поддерживает только эти флаги; не используйте другие [флаги Postgres].
-
В этой таблице приведено краткое описание влияния различных флагов (а также отсутствия флага, что
эквивалентно
s) на сопоставление символов новой строки и концов строк. Обратите внимание, что в Postgres флагиmиpне позволяют отрицательным символьным классам ([^xyz]) сопоставляться с символом новой строки, тогда как их аналоги в ClickHouse это ограничение не вводят. В остальном поведение ClickHouse такое же, как в Postgres: - Любые другие флаги, передаваемые функциям регулярных выражений, будут препятствовать pushdown функции.
-
Исключение —
regexp_replace(), которая также поддерживает флагg. Когда заданg, pg_clickhouse используетreplaceRegexpAll()вместоreplaceRegexpOne()и удаляет этот флаг перед добавлением остальных флагов. -
Аргумент replacement в Postgres
regexp_replace()поддерживает\&для ссылки на всё совпадение, тогда как в ClickHouse для ссылки на всё совпадение используется\0. Обязательно используйте\0, когда функция проталкивается в ClickHouse. -
Postgres
regexp_matchвозвращаетNULL, если совпадений нет, тогда как проталкиваемые выражения возвращают пустой массив. ИспользуйтеCOALESCE(), чтобы вместоNULLвозвращать пустой массив и тем самым сравнивать возвращаемые значения единообразно. Например:
to_char()
to_char() для timestamp и timestamp with time zone
проталкивается в ClickHouse formatDateTime только в том случае, если аргумент format
— это строковая константа, не равная NULL, и каждому ключевому слову PostgreSQL в ней
соответствует побайтно идентичный эквивалент в ClickHouse. Если формат задаётся динамически
(не Const) или содержит неподдерживаемое ключевое слово либо модификатор,
вызов переключается на локальное вычисление в PostgreSQL — pushdown никогда
не применяется при частичном переводе, поэтому вывод остаётся совместимым с PG.
Формы to_char() с двумя аргументами для numeric, interval и других
нетемпоральных типов никогда не проталкиваются; ClickHouse formatDateTime форматирует только
значения даты и времени.
Преобразованные ключевые слова
Текст в кавычках и литералы
"...", передаётся как есть; при этом любой символ %
удваивается до %%, чтобы экранировать префикс спецификатора ClickHouse. Последовательность \" вне
кавычек также передаётся как литеральный ". Внутри "..." обратная косая черта
экранирует только "; другие последовательности с обратной косой чертой трактуются как литеральный текст.
David E. Wheeler