diff options
Diffstat (limited to 'pygments/lexers/sql.py')
-rw-r--r-- | pygments/lexers/sql.py | 194 |
1 files changed, 191 insertions, 3 deletions
diff --git a/pygments/lexers/sql.py b/pygments/lexers/sql.py index 87dc638e..ace512be 100644 --- a/pygments/lexers/sql.py +++ b/pygments/lexers/sql.py @@ -30,6 +30,9 @@ - highlights errors in the output and notification levels; - handles psql backslash commands. + `PostgresExplainLexer` + A lexer to highlight Postgres execution plan. + The ``tests/examplefiles`` contains a few test files with data to be parsed by these lexers. @@ -45,7 +48,7 @@ from pygments.token import Punctuation, Whitespace, Text, Comment, Operator, \ from pygments.lexers import get_lexer_by_name, ClassNotFound from pygments.lexers._postgres_builtins import KEYWORDS, DATATYPES, \ - PSEUDO_TYPES, PLPGSQL_KEYWORDS + PSEUDO_TYPES, PLPGSQL_KEYWORDS, EXPLAIN_KEYWORDS from pygments.lexers._mysql_builtins import \ MYSQL_CONSTANTS, \ MYSQL_DATATYPES, \ @@ -57,8 +60,8 @@ from pygments.lexers import _tsql_builtins __all__ = ['PostgresLexer', 'PlPgsqlLexer', 'PostgresConsoleLexer', - 'SqlLexer', 'TransactSqlLexer', 'MySqlLexer', - 'SqliteConsoleLexer', 'RqlLexer'] + 'PostgresExplainLexer', 'SqlLexer', 'TransactSqlLexer', + 'MySqlLexer', 'SqliteConsoleLexer', 'RqlLexer'] line_re = re.compile('.*?\n') sqlite_prompt_re = re.compile(r'^(?:sqlite| ...)>(?= )') @@ -368,6 +371,191 @@ class PostgresConsoleLexer(Lexer): return +class PostgresExplainLexer(RegexLexer): + """ + Handle PostgreSQL EXPLAIN output + + """ + + name = 'PostgreSQL EXPLAIN dialect' + aliases = ['postgres-explain'] + filenames = ['*.explain'] + mimetypes = ['text/x-postgresql-explain'] + + tokens = { + 'root': [ + (r'(:|\(|\)|ms|kB|->|\.\.|\,)', Punctuation), + (r'(\s+)', Whitespace), + + # This match estimated cost and effectively measured counters with ANALYZE + # Then, we move to instrumentation state + (r'(cost)(=?)', bygroups(Name.Class, Punctuation), 'instrumentation'), + (r'(actual)( )(=?)', bygroups(Name.Class, Whitespace, Punctuation), 'instrumentation'), + + # Misc keywords + (words(('actual', 'Memory Usage', 'Memory', 'Buckets', 'Batches', + 'originally', 'row', 'rows', 'Hits', 'Misses', + 'Evictions', 'Overflows'), suffix=r'\b'), + Comment.Single), + + (r'(hit|read|dirtied|written|write|time|calls)(=)', bygroups(Comment.Single, Operator)), + (r'(shared|temp|local)', Keyword.Pseudo), + + # We move to sort state in order to emphasize specific keywords (especially disk access) + (r'(Sort Method)(: )', bygroups(Comment.Preproc, Punctuation), 'sort'), + + # These keywords can be followed by an object, like a table + (r'(Sort Key|Group Key|Presorted Key|Hash Key)(:)( )', + bygroups(Comment.Preproc, Punctuation, Whitespace), 'object_name'), + (r'(Cache Key|Cache Mode)(:)( )', bygroups(Comment, Punctuation, Whitespace), 'object_name'), + + # These keywords can be followed by a predicate + (words(('Join Filter', 'Subplans Removed', 'Filter', 'Merge Cond', + 'Hash Cond', 'Index Cond', 'Recheck Cond', 'Heap Blocks', + 'TID Cond', 'Run Condition', 'Order By', 'Function Call', + 'Table Function Call', 'Inner Unique', 'Params Evaluated', + 'Single Copy', 'Sampling', 'One-Time Filter', 'Output', + 'Relations', 'Remote SQL'), suffix=r'\b'), + Comment.Preproc, 'predicate'), + + # Special keyword to handle ON CONFLICT + (r'Conflict ', Comment.Preproc, 'conflict'), + + # Special keyword for InitPlan or SubPlan + (r'(InitPlan|SubPlan)( )(\d+)( )', + bygroups(Keyword, Whitespace, Number.Integer, Whitespace), + 'init_plan'), + + (words(('Sort Method', 'Join Filter', 'Planning time', + 'Planning Time', 'Execution time', 'Execution Time', + 'Workers Planned', 'Workers Launched', 'Buffers', + 'Planning', 'Worker', 'Query Identifier', 'Time', + 'Full-sort Groups'), suffix=r'\b'), Comment.Preproc), + + # Emphasize these keywords + + (words(('Rows Removed by Join Filter', 'Rows Removed by Filter', + 'Rows Removed by Index Recheck', + 'Heap Fetches', 'never executed'), + suffix=r'\b'), Name.Exception), + (r'(I/O Timings)(:)( )', bygroups(Name.Exception, Punctuation, Whitespace)), + + (words(EXPLAIN_KEYWORDS, suffix=r'\b'), Keyword), + + # join keywords + (r'((Right|Left|Full|Semi|Anti) Join)', Keyword.Type), + (r'(Parallel |Async |Finalize |Partial )', Comment.Preproc), + (r'Backward', Comment.Preproc), + (r'(Intersect|Except|Hash)', Comment.Preproc), + + (r'(CTE)( )(\w*)?', bygroups(Comment, Whitespace, Name.Variable)), + + + # Treat "on" and "using" as a punctuation + (r'(on|using)', Punctuation, 'object_name'), + + + # strings + (r"'(''|[^'])*'", String.Single), + # numbers + (r'\d+\.\d+', Number.Float), + (r'(\d+)', Number.Integer), + + # boolean + (r'(true|false)', Name.Constant), + # explain header + (r'\s*QUERY PLAN\s*\n\s*-+', Comment.Single), + # Settings + (r'(Settings)(:)( )', bygroups(Comment.Preproc, Punctuation, Whitespace), 'setting'), + + # Handle JIT counters + (r'(JIT|Functions|Options|Timing)(:)', bygroups(Comment.Preproc, Punctuation)), + (r'(Inlining|Optimization|Expressions|Deforming|Generation|Emission|Total)', Keyword.Pseudo), + + # Handle Triggers counters + (r'(Trigger)( )(\S*)(:)( )', + bygroups(Comment.Preproc, Whitespace, Name.Variable, Punctuation, Whitespace)), + + ], + 'expression': [ + # matches any kind of parenthesized expression + # the first opening paren is matched by the 'caller' + (r'\(', Punctuation, '#push'), + (r'\)', Punctuation, '#pop'), + (r'(never executed)', Name.Exception), + (r'[^)(]+', Comment), + ], + 'object_name': [ + + # This is a cost or analyze measure + (r'(\(cost)(=?)', bygroups(Name.Class, Punctuation), 'instrumentation'), + (r'(\(actual)( )(=?)', bygroups(Name.Class, Whitespace, Punctuation), 'instrumentation'), + + # if object_name is parenthesized, mark opening paren as + # punctuation, call 'expression', and exit state + (r'\(', Punctuation, 'expression'), + (r'(on)', Punctuation), + # matches possibly schema-qualified table and column names + (r'\w+(\.\w+)*( USING \S+| \w+ USING \S+)', Name.Variable), + (r'\"?\w+\"?(?:\.\"?\w+\"?)?', Name.Variable), + (r'\'\S*\'', Name.Variable), + + # if we encounter a comma, another object is listed + (r',\n', Punctuation, 'object_name'), + (r',', Punctuation, 'object_name'), + + # special case: "*SELECT*" + (r'"\*SELECT\*( \d+)?"(.\w+)?', Name.Variable), + (r'"\*VALUES\*(_\d+)?"(.\w+)?', Name.Variable), + (r'"ANY_subquery"', Name.Variable), + + # Variable $1 ... + (r'\$\d+', Name.Variable), + # cast + (r'::\w+', Name.Variable), + (r' +', Whitespace), + (r'"', Punctuation), + (r'\[\.\.\.\]', Punctuation), + (r'\)', Punctuation, '#pop'), + ], + 'predicate': [ + # if predicate is parenthesized, mark paren as punctuation + (r'(\()([^\n]*)(\))', bygroups(Punctuation, Name.Variable, Punctuation), '#pop'), + # otherwise color until newline + (r'[^\n]*', Name.Variable, '#pop'), + ], + 'instrumentation': [ + (r'=|\.\.', Punctuation), + (r' +', Whitespace), + (r'(rows|width|time|loops)', Name.Class), + (r'\d+\.\d+', Number.Float), + (r'(\d+)', Number.Integer), + (r'\)', Punctuation, '#pop'), + ], + 'conflict': [ + (r'(Resolution: )(\w+)', bygroups(Comment.Preproc, Name.Variable)), + (r'(Arbiter \w+:)', Comment.Preproc, 'object_name'), + (r'(Filter: )', Comment.Preproc, 'predicate'), + ], + 'setting': [ + (r'([a-z_]*?)(\s*)(=)(\s*)(\'.*?\')', bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String)), + (r'\, ', Punctuation), + ], + 'init_plan': [ + (r'\(', Punctuation), + (r'returns \$\d+(,\$\d+)?', Name.Variable), + (r'\)', Punctuation, '#pop'), + ], + 'sort': [ + (r':|kB', Punctuation), + (r'(quicksort|top-N|heapsort|Average|Memory|Peak)', Comment.Prepoc), + (r'(external|merge|Disk|sort)', Name.Exception), + (r'(\d+)', Number.Integer), + (r' +', Whitespace), + ], + } + + class SqlLexer(RegexLexer): """ Lexer for Structured Query Language. Currently, this lexer does |