Ostatnimi czasy musiałem umieścić w dokumencie utworzonym przy użyciu Latex przykłady zapytań dla bazy danych Oracle. W związku z czym sięgnąłem do pakietu listings, który pozwala na odpowiednie wyróżnienie słów kluczowych SQL.

Dokładny sposób włączenia tego pakietu i jego podstawowe parametry można znaleźć we tym wpisie: Kolorowanie składni Javy w systemie LaTeX przygotowanym przez Marcina Stachniuka. Dla Oracle deklaracja może wyglądać tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
\documentclass[a4paper,12pt]{article}

\usepackage[T1]{fontenc}
\usepackage[utf8x]{inputenc}

\lstset{language=SQL,
    basicstyle=\small,
    showstringspaces=false,
    tabsize=4,
    inputencoding=utf8x,
    extendedchars=\true,
}

\begin{document}

\begin{lstlisting}
    SELECT MIN(DISTINCT kolumna2)
    FROM table
    WHERE NVL(kolumna, 0) = 0
\end{lstlisting}

\end{document
}

Taki zapis spowoduje wygenerowanie zapytania SQL, gdzie słowa kluczowe oraz niektóre funkcje zostaną wypisane czcionką pogrubioną, pozostałem elementy natomiast zwykłą. Także nie wszystkie elementy Oracle SQL zostają poprawnie zakwalifikowane i część z nich wyświetla się w „zwykły” sposób.

Drugim problem są nazwy funkcji, ja chciałbym aby zostały one wyświetlone pochyłą czcionką a nie pogrubioną.

Najprostszym rozwiązaniem oczywiście byłoby dodanie odpowiednich słów kluczowych zarówno z deklaracji języka, oraz utworzenie odpowiedniej klasy dla funkcji, które byłby wypisywane kursywą. Nie można jednak zmienić przyporządkowania już przypisanych funkcji, także mogę np. wypisać funkcję NVL kursywą, ale już z funkcją MIN mi się to nie uda.

Pozostaje więc zdefiniowanie nowego języka, który będzie zachowywał się tak, jak tego oczekuję. Nie jest to trudne, tym bardziej, że dysonujemy już przecież definicją dla języka SQL, wystarczy tylko odpowiednio ją zmodyfikować.

Najpierw należy odszukujemy definicję języka SQL, w moim przypadku znajduje się ona w tym pliku /usr/share/texmf-texlive/tex/latex/listings/lstlang1.sty:

1393
1394
1395
1396
1397
1398
1399
1400
1401
%%
%% SQL definition (c) 1998 Christian Haul
%%                (c) 2002 Neil Conway
%%                (c) 2002 Robert Frank
%%                (c) 2003 Dirk Jesko
%%
\lst@definelanguage{SQL}%
    {morekeywords={ABSOLUTE,ACTION,ADD,ALLOCATE,ALTER,ARE,AS,ASSERTION,%
       AT,BETWEEN,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,%

Pozostaje teraz zdefiniować własny dialekt, skopiować definicję dialektu SQL oraz ją zmodyfikować. Poniżej moja propozycja:

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
\lstdefinelanguage[Oracle]{SQL}%
  {morekeywords={ABSOLUTE,ACTION,ADD,ALLOCATE,ALTER,ARE,AS,ASSERTION,%
      AT,BETWEEN,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,%
      CATALOG,CHAR_LENGTH,CHARACTER_LENGTH,CLUSTER,%
      COLLATION,COLUMN,CONNECT,CONNECTION,CONSTRAINT,%
      CONSTRAINTS,CONVERT,CORRESPONDING,CREATE,CROSS,CURRENT_DATE,%
      CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,DAY,DEALLOCATE,%
      DEC,DEFERRABLE,DEFERED,DESCRIBE,DESCRIPTOR,DIAGNOSTICS,%
      DISCONNECT,DOMAIN,DROP,ELSE,END,EXEC,EXCEPT,EXCEPTION,EXECUTE,%
      EXTERNAL,EXTRACT,FALSE,FIRST,FOREIGN,FROM,FULL,GET,GLOBAL,%
      GRAPHIC,HAVING,HOUR,IDENTITY,IMMEDIATE,INDEX,INITIALLY,INNER,%
      INPUT,INSENSITIVE,INSERT,INTO,INTERSECT,INTERVAL,%
      ISOLATION,JOIN,KEY,LAST,LEADING,LEFT,LEVEL,LIMIT,LOCAL,%
      MATCH,MINUTE,MONTH,NAMES,NATIONAL,NATURAL,NCHAR,NEXT,NO,NOT,NULL,%
      OCTET_LENGTH,ON,ONLY,ORDER,ORDERED,OUTER,OUTPUT,OVERLAPS,%
      PAD,PARTIAL,POSITION,PREPARE,PRESERVE,PRIMARY,PRIOR,READ,%
      RELATIVE,RESTRICT,REVOKE,RIGHT,ROWS,SCROLL,SECOND,SELECT,SESSION,%
      SESSION_USER,SIZE,SPACE,SQLSTATE,SUBSTRING,SYSTEM_USER,%
      TABLE,TEMPORARY,THEN,TIMEZONE_HOUR,%
      TIMEZONE_MINUTE,TRAILING,TRANSACTION,TRANSLATION,%
      TRUE,UNIQUE,UNKNOWN,USAGE,USING,VALUE,VALUES,%
      VARGRAPHIC,VARYING,WHEN,WHERE,WRITE,YEAR,ZONE,%
      AND,ASC,CHECK,COMMIT,DESC,DISTINCT,GROUP,IN,% FF
      LIKE,NUMBER,ROLLBACK,VARCHAR2,% FF
      UNION,UPDATE,% RF
      ALL,ANY,CUBE,CUBE,DEFAULT,DELETE,EXISTS,GRANT,OR,RECURSIVE,% DJ
      ROLE,ROLLUP,SET,SOME,TRIGGER,VIEW},% DJ
   morendkeywords={BIT,BLOB,CHAR,CHARACTER,CLOB,DATE,DECIMAL,FLOAT,% DJ
      INT,INTEGER,NUMERIC,SMALLINT,TIME,TIMESTAMP,VARCHAR},% moved here
   sensitive=false,% DJ
   morecomment=[l]--,%
   morecomment=[s]{/*}{*/},%
   morestring=[d]',%
   morestring=[d]",%

  %% dodatkowe słowa kluczowe dla Oracla oraz definicja funkcji

    morekeywords={SAMPLE,SEED,ROWNUM,SYSDATE,IS,MINUS,WITH,START,%
      PARTITION,OVER,UNBOUNDED,PRECEDING,CURRENT,ROW,FOLLOWING,%
      MODIFY,RENAME,TO,REFERENCES,DISABLE,ENABLE,NOVALIDATE,EXCEPTIONS,%
      TRUNCATE,MERGE,MATCHED,SAVEPOINT,FORCE,OPTION,SEQUENCE,CYCLE,%
      INCREMENT,MINVALUE,MAXVALUE},
    classoffset=1,
    morekeywords={dbms_random,LOWER,UPPER,INITCAP,LPAD,RPAD,LTRIM,%
      RTRIM,TRIM,SUBSTR,SUM,MIN,MAX,INSTR,LENGTH,ROUND,TRUNC,POWER,%
      SQRT,SIGN,ABS,MOD,NULLIF,SIN,COS,TAN,SINH,COSH,TANH,REPLACE,%
      TRANSLATE,TO_CHAR,TO_NUMBER,DECODE,NVL,NVL2,COALESCE,AVG,%
      COLLATE,COUNT,STDDEV,VARIANCE,MEDIAN,MONTHS_BETWEEN,ADD_MONTHS,%
      LAST_DAY,NLSSORT,TO_DATE,GROUPING,RANK,DENSE_RANK,ROW_NUMBER,%
      FIRST_VALUE,LAST_VALUE,LEAD,LAG},
    keywordstyle=\textit,
  }[keywords,comments,strings]%

Oczywiście, każdy będzie musiał dostosować ten zapis pod siebie w zależności od funkcji które są używane, być może o niektórych słowach kluczowych SQL także zapomniałem. Należy pamiętać, że jeżeli jakieś słowo jest wypisywane pogrubionymi literami (a np. powinno być funkcją i chcemy aby zostało zapisane kursywą), to należy sprawdzić czy jest ono zdefiniowane w jakieś wcześniejszej linii. W takim przypadku należy je stamtąd usunąć.

Użycie takiego dialektu wygląda tak:

6
7
8
9
10
11
12
\lstset{language=[Oracle]SQL,
    basicstyle=\small,
    showstringspaces=false,
    tabsize=4,
    inputencoding=utf8x,
    extendedchars=\true,
}

Źródła