Подсветка синтаксиса PostgreSQL

habr.png

Спешу поделиться хорошей новостью: жизнь авторов статей про PostgreSQL и их читателей стала немного лучше.

Как знают все хаброписатели, для оформления исходного кода используется специальный тег , который подсвечивает синтаксис. Не секрет также, что подсветка не всегда получается идеальной, и тогда авторы (которым не все равно, как выглядят их статьи) вынуждены заниматься самодеятельностью — расцвечивать свой код с помощью .

Особенно печально все было с PostgreSQL, поскольку подсветка охватывала более или менее стандартный SQL и категорически не понимала специфики нашей СУБД. Шло время, Алексей boomburum старательно исправлял мои font-ы на source (а я — обратно), пока не стало очевидно, что подсветку надо чинить. Наконец Далер daleraliyorov подсказал выход: добавить поддержку PostgreSQL в библиотеку highlightjs, которой пользуется Хабр. И вот — готово, встречайте.

pgsql: SQL, PL/pgSQL и все-все-все


Итак, секрет правильной подсветки — в новом языке pgsql. Его можно выбрать в меню (кнопка «исходный код») или указать вручную. В html для этого надо написать


мой код


а в markdown — так:

```pgsql
мой код
```

В принципе, highlightjs умеет определять язык сама, но нормально это работает только для больших фрагментов кода; на маленьких кусочках автоопределение часто промахивается. Кроме того, автоопределение требует времени, так что если указать язык явно, код быстрее заиграет красками.

Например, чтобы получить

CREATE TABLE aircrafts_data (
    aircraft_code character(3) NOT NULL,
    model jsonb NOT NULL,
    range integer NOT NULL,
    CONSTRAINT aircrafts_range_check CHECK ((range > 0))
);


мы пишем


CREATE TABLE aircrafts_data (
    aircraft_code character(3) NOT NULL,
    model jsonb NOT NULL,
    range integer NOT NULL,
    CONSTRAINT aircrafts_range_check CHECK ((range > 0))
);


Тот же самый язык pgsql расцвечивает и код на PL/pgSQL. Например, чтобы получить

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS $$
BEGIN
  RETURN QUERY
    SELECT flightid FROM flight WHERE flightdate >= $1 AND flightdate < ($1 + 1);
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Нет рейсов на дату: %.', $1;
  END IF;
  RETURN;
END
$$ LANGUAGE plpgsql;


пишем


CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS $$
...
$$ LANGUAGE plpgsql;


Небольшая тонкость состоит в том, что символьные строки, заключенные в доллары, всегда подсвечиваются как код, а строки в апострофах не подсвечиваются никогда. Я рассматривал разные варианты, но именно этот показался наиболее адекватным.

Способность highlightjs автоматически определять язык фрагмента позволяет подсвечивать функции и на других языках. Например, все будет работать и для PL/Perl:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    my ($x, $y) = @_;
    if (not defined $x) {
        return undef if not defined $y;
        return $y;
    }
    return $x if not defined $y;
    return $x if $x > $y;
    return $y;
$$ LANGUAGE plperl;


Для этого не нужно ничего специального, просто пишем


CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
...
$$ LANGUAGE plperl;


Конечно, выбранный язык зависит только от того, что написано внутри долларов, и никак не определяется тем, что написано после LANGUAGE.

В целом подсветка соответствует вышедшей недавно 11-й версии PostgreSQL.

Много сомнений было насчет подсветки функций. К сожалению, чтобы отличить имя функции от, например, имени таблицы, нужен полноценный синтаксический разбор, а в рамках подсветки синтаксиса это не решается. Можно составить длинный список стандартных функций и расцвечивать их, но как быть с функциями из многочисленных расширений? В итоге решил не расцвечивать вовсе — все равно все держится на ключевых словах, а пестроты поубавилось.

plaintext: текст, просто текст


Иногда в статье требуется оформить результаты запроса. Конечно, никаких ключевых слов там нет, подсвечивать ничего не нужно, но хочется, чтобы текст смотрелся «консольно», так же, как код. Для этого теперь можно использовать специальный язык plaintext. Например, чтобы получить

WITH xmldata(data) AS (VALUES ($$

 
 
 
$$::xml)
)
SELECT xmltable.*
  FROM XMLTABLE(XMLNAMESPACES('http://example.com/myns' AS x,
                              'http://example.com/b' AS "B"),
             '/x:example/x:item'
                PASSING (SELECT data FROM xmldata)
                COLUMNS foo int PATH '@foo',
                  bar int PATH '@B:bar');

 foo | bar
-----+-----
   1 |   2
   3 |   4
   4 |   5
(3 rows)


пишем


WITH xmldata(data) AS (VALUES ($$
...


 foo | bar
-----+-----
   1 |   2
   3 |   4
   4 |   5
(3 rows)


Plaintext всегда надо указывать явно, автоматически он не определяется.

Надеюсь, что нововведение вам понравится и пригодится. Если найдете ошибку в том, как подсвечивается код (а ошибки неизбежны, слишком уж контекстно-зависимый синтаксис у SQL), создавайте задачу на github проекта, а еще лучше — предлагайте решение.

P.S. Не забывайте про конференцию PGConf, которая состоится 4–6 февраля в Москве. Заявки на доклады принимаются до 5 декабря!

© Habrahabr.ru