|
30 | 30 | - highlights errors in the output and notification levels; |
31 | 31 | - handles psql backslash commands. |
32 | 32 |
|
| 33 | + `PostgresExplainLexer` |
| 34 | + A lexer to highlight Postgres execution plan. |
| 35 | +
|
33 | 36 | The ``tests/examplefiles`` contains a few test files with data to be |
34 | 37 | parsed by these lexers. |
35 | 38 |
|
|
45 | 48 | from pygments.lexers import get_lexer_by_name, ClassNotFound |
46 | 49 |
|
47 | 50 | from pygments.lexers._postgres_builtins import KEYWORDS, DATATYPES, \ |
48 | | - PSEUDO_TYPES, PLPGSQL_KEYWORDS |
| 51 | + PSEUDO_TYPES, PLPGSQL_KEYWORDS, EXPLAIN_KEYWORDS |
49 | 52 | from pygments.lexers._mysql_builtins import \ |
50 | 53 | MYSQL_CONSTANTS, \ |
51 | 54 | MYSQL_DATATYPES, \ |
|
57 | 60 |
|
58 | 61 |
|
59 | 62 | __all__ = ['PostgresLexer', 'PlPgsqlLexer', 'PostgresConsoleLexer', |
60 | | - 'SqlLexer', 'TransactSqlLexer', 'MySqlLexer', |
61 | | - 'SqliteConsoleLexer', 'RqlLexer'] |
| 63 | + 'PostgresExplainLexer', 'SqlLexer', 'TransactSqlLexer', |
| 64 | + 'MySqlLexer', 'SqliteConsoleLexer', 'RqlLexer'] |
62 | 65 |
|
63 | 66 | line_re = re.compile('.*?\n') |
64 | 67 | sqlite_prompt_re = re.compile(r'^(?:sqlite| ...)>(?= )') |
@@ -368,6 +371,191 @@ def get_tokens_unprocessed(self, data): |
368 | 371 | return |
369 | 372 |
|
370 | 373 |
|
| 374 | +class PostgresExplainLexer(RegexLexer): |
| 375 | + """ |
| 376 | + Handle PostgreSQL EXPLAIN output |
| 377 | +
|
| 378 | + """ |
| 379 | + |
| 380 | + name = 'PostgreSQL EXPLAIN dialect' |
| 381 | + aliases = ['postgres-explain'] |
| 382 | + filenames = ['*.explain'] |
| 383 | + mimetypes = ['text/x-postgresql-explain'] |
| 384 | + |
| 385 | + tokens = { |
| 386 | + 'root': [ |
| 387 | + (r'(:|\(|\)|ms|kB|->|\.\.|\,)', Punctuation), |
| 388 | + (r'(\s+)', Whitespace), |
| 389 | + |
| 390 | + # This match estimated cost and effectively measured counters with ANALYZE |
| 391 | + # Then, we move to instrumentation state |
| 392 | + (r'(cost)(=?)', bygroups(Name.Class, Punctuation), 'instrumentation'), |
| 393 | + (r'(actual)( )(=?)', bygroups(Name.Class, Whitespace, Punctuation), 'instrumentation'), |
| 394 | + |
| 395 | + # Misc keywords |
| 396 | + (words(('actual', 'Memory Usage', 'Memory', 'Buckets', 'Batches', |
| 397 | + 'originally', 'row', 'rows', 'Hits', 'Misses', |
| 398 | + 'Evictions', 'Overflows'), suffix=r'\b'), |
| 399 | + Comment.Single), |
| 400 | + |
| 401 | + (r'(hit|read|dirtied|written|write|time|calls)(=)', bygroups(Comment.Single, Operator)), |
| 402 | + (r'(shared|temp|local)', Keyword.Pseudo), |
| 403 | + |
| 404 | + # We move to sort state in order to emphasize specific keywords (especially disk access) |
| 405 | + (r'(Sort Method)(: )', bygroups(Comment.Preproc, Punctuation), 'sort'), |
| 406 | + |
| 407 | + # These keywords can be followed by an object, like a table |
| 408 | + (r'(Sort Key|Group Key|Presorted Key|Hash Key)(:)( )', |
| 409 | + bygroups(Comment.Preproc, Punctuation, Whitespace), 'object_name'), |
| 410 | + (r'(Cache Key|Cache Mode)(:)( )', bygroups(Comment, Punctuation, Whitespace), 'object_name'), |
| 411 | + |
| 412 | + # These keywords can be followed by a predicate |
| 413 | + (words(('Join Filter', 'Subplans Removed', 'Filter', 'Merge Cond', |
| 414 | + 'Hash Cond', 'Index Cond', 'Recheck Cond', 'Heap Blocks', |
| 415 | + 'TID Cond', 'Run Condition', 'Order By', 'Function Call', |
| 416 | + 'Table Function Call', 'Inner Unique', 'Params Evaluated', |
| 417 | + 'Single Copy', 'Sampling', 'One-Time Filter', 'Output', |
| 418 | + 'Relations', 'Remote SQL'), suffix=r'\b'), |
| 419 | + Comment.Preproc, 'predicate'), |
| 420 | + |
| 421 | + # Special keyword to handle ON CONFLICT |
| 422 | + (r'Conflict ', Comment.Preproc, 'conflict'), |
| 423 | + |
| 424 | + # Special keyword for InitPlan or SubPlan |
| 425 | + (r'(InitPlan|SubPlan)( )(\d+)( )', |
| 426 | + bygroups(Keyword, Whitespace, Number.Integer, Whitespace), |
| 427 | + 'init_plan'), |
| 428 | + |
| 429 | + (words(('Sort Method', 'Join Filter', 'Planning time', |
| 430 | + 'Planning Time', 'Execution time', 'Execution Time', |
| 431 | + 'Workers Planned', 'Workers Launched', 'Buffers', |
| 432 | + 'Planning', 'Worker', 'Query Identifier', 'Time', |
| 433 | + 'Full-sort Groups'), suffix=r'\b'), Comment.Preproc), |
| 434 | + |
| 435 | + # Emphasize these keywords |
| 436 | + |
| 437 | + (words(('Rows Removed by Join Filter', 'Rows Removed by Filter', |
| 438 | + 'Rows Removed by Index Recheck', |
| 439 | + 'Heap Fetches', 'never executed'), |
| 440 | + suffix=r'\b'), Name.Exception), |
| 441 | + (r'(I/O Timings)(:)( )', bygroups(Name.Exception, Punctuation, Whitespace)), |
| 442 | + |
| 443 | + (words(EXPLAIN_KEYWORDS, suffix=r'\b'), Keyword), |
| 444 | + |
| 445 | + # join keywords |
| 446 | + (r'((Right|Left|Full|Semi|Anti) Join)', Keyword.Type), |
| 447 | + (r'(Parallel |Async |Finalize |Partial )', Comment.Preproc), |
| 448 | + (r'Backward', Comment.Preproc), |
| 449 | + (r'(Intersect|Except|Hash)', Comment.Preproc), |
| 450 | + |
| 451 | + (r'(CTE)( )(\w*)?', bygroups(Comment, Whitespace, Name.Variable)), |
| 452 | + |
| 453 | + |
| 454 | + # Treat "on" and "using" as a punctuation |
| 455 | + (r'(on|using)', Punctuation, 'object_name'), |
| 456 | + |
| 457 | + |
| 458 | + # strings |
| 459 | + (r"'(''|[^'])*'", String.Single), |
| 460 | + # numbers |
| 461 | + (r'\d+\.\d+', Number.Float), |
| 462 | + (r'(\d+)', Number.Integer), |
| 463 | + |
| 464 | + # boolean |
| 465 | + (r'(true|false)', Name.Constant), |
| 466 | + # explain header |
| 467 | + (r'\s*QUERY PLAN\s*\n\s*-+', Comment.Single), |
| 468 | + # Settings |
| 469 | + (r'(Settings)(:)( )', bygroups(Comment.Preproc, Punctuation, Whitespace), 'setting'), |
| 470 | + |
| 471 | + # Handle JIT counters |
| 472 | + (r'(JIT|Functions|Options|Timing)(:)', bygroups(Comment.Preproc, Punctuation)), |
| 473 | + (r'(Inlining|Optimization|Expressions|Deforming|Generation|Emission|Total)', Keyword.Pseudo), |
| 474 | + |
| 475 | + # Handle Triggers counters |
| 476 | + (r'(Trigger)( )(\S*)(:)( )', |
| 477 | + bygroups(Comment.Preproc, Whitespace, Name.Variable, Punctuation, Whitespace)), |
| 478 | + |
| 479 | + ], |
| 480 | + 'expression': [ |
| 481 | + # matches any kind of parenthesized expression |
| 482 | + # the first opening paren is matched by the 'caller' |
| 483 | + (r'\(', Punctuation, '#push'), |
| 484 | + (r'\)', Punctuation, '#pop'), |
| 485 | + (r'(never executed)', Name.Exception), |
| 486 | + (r'[^)(]+', Comment), |
| 487 | + ], |
| 488 | + 'object_name': [ |
| 489 | + |
| 490 | + # This is a cost or analyze measure |
| 491 | + (r'(\(cost)(=?)', bygroups(Name.Class, Punctuation), 'instrumentation'), |
| 492 | + (r'(\(actual)( )(=?)', bygroups(Name.Class, Whitespace, Punctuation), 'instrumentation'), |
| 493 | + |
| 494 | + # if object_name is parenthesized, mark opening paren as |
| 495 | + # punctuation, call 'expression', and exit state |
| 496 | + (r'\(', Punctuation, 'expression'), |
| 497 | + (r'(on)', Punctuation), |
| 498 | + # matches possibly schema-qualified table and column names |
| 499 | + (r'\w+(\.\w+)*( USING \S+| \w+ USING \S+)', Name.Variable), |
| 500 | + (r'\"?\w+\"?(?:\.\"?\w+\"?)?', Name.Variable), |
| 501 | + (r'\'\S*\'', Name.Variable), |
| 502 | + |
| 503 | + # if we encounter a comma, another object is listed |
| 504 | + (r',\n', Punctuation, 'object_name'), |
| 505 | + (r',', Punctuation, 'object_name'), |
| 506 | + |
| 507 | + # special case: "*SELECT*" |
| 508 | + (r'"\*SELECT\*( \d+)?"(.\w+)?', Name.Variable), |
| 509 | + (r'"\*VALUES\*(_\d+)?"(.\w+)?', Name.Variable), |
| 510 | + (r'"ANY_subquery"', Name.Variable), |
| 511 | + |
| 512 | + # Variable $1 ... |
| 513 | + (r'\$\d+', Name.Variable), |
| 514 | + # cast |
| 515 | + (r'::\w+', Name.Variable), |
| 516 | + (r' +', Whitespace), |
| 517 | + (r'"', Punctuation), |
| 518 | + (r'\[\.\.\.\]', Punctuation), |
| 519 | + (r'\)', Punctuation, '#pop'), |
| 520 | + ], |
| 521 | + 'predicate': [ |
| 522 | + # if predicate is parenthesized, mark paren as punctuation |
| 523 | + (r'(\()([^\n]*)(\))', bygroups(Punctuation, Name.Variable, Punctuation), '#pop'), |
| 524 | + # otherwise color until newline |
| 525 | + (r'[^\n]*', Name.Variable, '#pop'), |
| 526 | + ], |
| 527 | + 'instrumentation': [ |
| 528 | + (r'=|\.\.', Punctuation), |
| 529 | + (r' +', Whitespace), |
| 530 | + (r'(rows|width|time|loops)', Name.Class), |
| 531 | + (r'\d+\.\d+', Number.Float), |
| 532 | + (r'(\d+)', Number.Integer), |
| 533 | + (r'\)', Punctuation, '#pop'), |
| 534 | + ], |
| 535 | + 'conflict': [ |
| 536 | + (r'(Resolution: )(\w+)', bygroups(Comment.Preproc, Name.Variable)), |
| 537 | + (r'(Arbiter \w+:)', Comment.Preproc, 'object_name'), |
| 538 | + (r'(Filter: )', Comment.Preproc, 'predicate'), |
| 539 | + ], |
| 540 | + 'setting': [ |
| 541 | + (r'([a-z_]*?)(\s*)(=)(\s*)(\'.*?\')', bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String)), |
| 542 | + (r'\, ', Punctuation), |
| 543 | + ], |
| 544 | + 'init_plan': [ |
| 545 | + (r'\(', Punctuation), |
| 546 | + (r'returns \$\d+(,\$\d+)?', Name.Variable), |
| 547 | + (r'\)', Punctuation, '#pop'), |
| 548 | + ], |
| 549 | + 'sort': [ |
| 550 | + (r':|kB', Punctuation), |
| 551 | + (r'(quicksort|top-N|heapsort|Average|Memory|Peak)', Comment.Prepoc), |
| 552 | + (r'(external|merge|Disk|sort)', Name.Exception), |
| 553 | + (r'(\d+)', Number.Integer), |
| 554 | + (r' +', Whitespace), |
| 555 | + ], |
| 556 | + } |
| 557 | + |
| 558 | + |
371 | 559 | class SqlLexer(RegexLexer): |
372 | 560 | """ |
373 | 561 | Lexer for Structured Query Language. Currently, this lexer does |
|
0 commit comments