Skip to main content
author:

Lele Gaifax

contact:

lele@metapensiero.it

license:

GNU General Public License version 3 or later

This is a Python 3 implementation of a wrapper to libpg_query, a C library that repackages the PostgreSQL languages parser as a standalone static library.

I needed a better SQL reformatter than the one implemented by sqlparse, and was annoyed by a few glitches (subselects in particular) that ruins the otherwise excellent job it does, considering that it is a generic library that tries to swallow many different SQL dialects.

When I found psqlparse I decided to try implementing a PostgreSQL focused tool: at the beginning it’s been easier than I feared, but I quickly hit some shortcomings in that implementation, so I opted for writing my own solution restarting from scratch, with the following goals:

  • target only Python 3.4+

  • target PostgreSQL 10, taking advantage of a work-in-progress branch of the libpg_query library

  • use a more dynamic approach to represent the parse tree, with a twofold advantage:

    1. it is much less boring to code, because there’s no need to write one Python class for each PostgreSQL node tag

    2. the representation is version agnostic, it can be adapted to newer/older Elephants in a snap

  • allow exploration of parse tree in both directions, because I realized that some kinds of nodes require that knowledge to determine their textual representation

  • avoid introducing arbitrary renames of tags and attributes, so what you read in PostgreSQL documentation/sources[*] is available without the hassle of guessing how a symbol has been mapped

  • use a zero copy approach, keeping the original parse tree returned from the underlying libpg_query functions and have each node just borrow a reference to its own subtree

Introduction

At the lower level the module exposes two libpg_query functions, parse_sql() and parse_plpgsql(), that take respectively an SQL statement and a PLpgSQL statement and return a parse tree as a hierarchy of Python dictionaries, lists and scalar values. In some cases these scalars correspond to some C typedef enums, that are automatically extracted from the PostgreSQL headers and are available as pg_query.enums.

At a higher level that tree is represented by three Python classes, a Node that represents a single node, a List that wraps a sequence of nodes and a Scalar for plain values such a strings, integers, booleans or none.

Every node is identified by a tag, a string label that characterize its content that is exposed as a set of attributes as well as with a dictionary-like interface (technically they implements both a __getattr__ method and a __getitem__ method). When asked for an attribute, the node returns an instance of the base classes, i.e. another Node, or a List or a Scalar, depending on the data type of that item. When the node does not contain the requested attribute it returns a singleton Missing marker instance.

A List wraps a plain Python list and may contains a sequence of Node instances, or in some cases other sub-lists, that can be accessed with the usual syntax, or iterated.

Finally, a Scalar carries a single value of some type, accessible through its value attribute.

On top of that, the module implements two serializations, one that transforms a Node into a raw textual representation and another that returns a prettified representation. The latter is exposed by the __main__ entry point of the package, see below for an example.

Installation

As usual, the easiest way is with pip:

$ pip install pg_query

Alternatively you can clone the repository:

$ git clone https://github.com/lelit/pg_query.git --recursive

and install from there:

$ pip install ./pg_query

Development

There is a set of makefiles implementing the most common operations, a make help will show a brief table of contents. A comprehensive test suite, based on pytest, covers 98% of the source lines.

Examples of usage

  • Parse an SQL statement and get its AST root node:

    >>> from pg_query import Node, parse_sql
    >>> root = Node(parse_sql('SELECT foo FROM bar'))
    >>> print(root)
    None=[1*{RawStmt}]
  • Recursively traverse the parse tree:

    >>> for node in root.traverse():
    ...   print(node)
    ...
    None[0]={RawStmt}
    stmt={SelectStmt}
    fromClause[0]={RangeVar}
    inh=<True>
    location=<16>
    relname=<'bar'>
    relpersistence=<'p'>
    op=<0>
    targetList[0]={ResTarget}
    location=<7>
    val={ColumnRef}
    fields[0]={String}
    str=<'foo'>
    location=<7>

    As you can see, the representation of each value is mnemonic: {some_tag} means a Node with tag some_tag, [X*{some_tag}] is a List containing X nodes of that particular kind[] and <value> is a Scalar.

  • Get a particular node:

    >>> from_clause = root[0].stmt.fromClause
    >>> print(from_clause)
    fromClause=[1*{RangeVar}]
  • Obtain some information about a node:

    >>> range_var = from_clause[0]
    >>> print(range_var.node_tag)
    RangeVar
    >>> print(range_var.attribute_names)
    dict_keys(['relname', 'inh', 'relpersistence', 'location'])
    >>> print(range_var.parent_node)
    stmt={SelectStmt}
  • Iterate over nodes:

    >>> for a in from_clause:
    ...     print(a)
    ...     for b in a:
    ...         print(b)
    ...
    fromClause[0]={RangeVar}
    inh=<True>
    location=<16>
    relname=<'bar'>
    relpersistence=<'p'>
  • Reformat a SQL statement[] from the command line:

    $ echo "select a,b,c from sometable" | pgpp
    SELECT a
         , b
         , c
    FROM sometable
    
    $ echo 'update "table" set value=123 where value is null' | pgpp
    UPDATE "table"
    SET value = 123
    WHERE value IS NULL
    
    $ echo "
    insert into t (id, description)
    values (1, 'this is short enough'),
           (2, 'this is too long, and will be splitted')" | pgpp -s 20
    INSERT INTO t (id, description)
    VALUES (1, 'this is short enough')
         , (2, 'this is too long, an'
               'd will be splitted')
  • Programmatically reformat a SQL statement:

    >>> from pg_query import prettify
    >>> print(prettify('delete from sometable where value is null'))
    DELETE FROM sometable
    WHERE value IS NULL

Documentation

Latest documentation is hosted by Read the Docs at http://pg-query.readthedocs.io/en/latest/

Changes

0.19 (2017-11-16)

  • Fix serialization of column labels containing double quotes

  • Fix corner issues surfaced implementing some more DDL statement printers

0.18 (2017-11-14)

  • Fix endless loop due to sloppy conversion of command line option

  • Install the command line tool as pgpp

0.17 (2017-11-12)

  • Rename printers.sql to printers.dml (backward incompatibility)

  • List printer functions in the documentation, referencing the definition of related node type

  • Fix inconsistent spacing in JOIN condition inside a nested expression

  • Fix representation of unbound arrays

  • Fix representation of interval data type

  • Initial support for DDL statements

  • Fix representation of string literals containing single quotes

0.16 (2017-10-31)

  • Update libpg_query to 10-1.0.0

0.15 (2017-10-12)

  • Fix indentation of boolean expressions in SELECT’s targets (issue #3)

0.14 (2017-10-09)

  • Update to latest libpg_query’s 10-latest branch, targeting PostgreSQL 10.0 final

0.13 (2017-09-17)

  • Fix representation of subselects requiring surrounding parens

0.12 (2017-08-22)

  • New option --version on the command line tool

  • Better enums documentation

  • Release the GIL while calling libpg_query functions

0.11 (2017-08-11)

  • Nicer indentation for JOINs, making OUTER JOINs stand out

  • Minor tweaks to lists rendering, with less spurious whitespaces

  • New option --no-location on the command line tool

0.10 (2017-08-11)

  • Support Python 3.4 and Python 3.5 as well as Python 3.6

0.9 (2017-08-10)

  • Fix spacing before the $ character

  • Handle type modifiers

  • New option --plpgsql on the command line tool, just for fun

0.8 (2017-08-10)

  • Add enums subpackages to the documentation with references to their related headers

  • New compact_lists_margin option to produce a more compact representation when possible (see issue #1)

0.7 (2017-08-10)

  • Fix sdist including the Sphinx documentation

0.6 (2017-08-10)

  • New option --parse-tree on the command line tool to show just the parse tree

  • Sphinx documentation, available online

0.5 (2017-08-09)

  • Handle some more cases when a name must be double-quoted

  • Complete the serialization of the WindowDef node, handling its frame options

0.4 (2017-08-09)

  • Expose the actual PostgreSQL version the underlying libpg_query libray is built on thru a new get_postgresql_version() function

  • New option safety_belt for the prettify() function, to protect the innocents

  • Handle serialization of CoalesceExpr and MinMaxExpr

0.3 (2017-08-07)

  • Handle serialization of ParamRef nodes

  • Expose a prettify() helper function

0.2 (2017-08-07)

  • Test coverage at 99%

  • First attempt at automatic wheel upload to PyPI, let’s see…

0.1 (2017-08-07)

  • First release (“Hi daddy!”, as my soul would tag it)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pg_query-0.19.tar.gz (1.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pg_query-0.19-cp36-cp36m-manylinux1_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.6m

pg_query-0.19-cp36-cp36m-manylinux1_i686.whl (1.0 MB view details)

Uploaded CPython 3.6m

pg_query-0.19-cp35-cp35m-manylinux1_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.5m

pg_query-0.19-cp35-cp35m-manylinux1_i686.whl (1.0 MB view details)

Uploaded CPython 3.5m

pg_query-0.19-cp34-cp34m-manylinux1_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.4m

pg_query-0.19-cp34-cp34m-manylinux1_i686.whl (1.0 MB view details)

Uploaded CPython 3.4m

File details

Details for the file pg_query-0.19.tar.gz.

File metadata

  • Download URL: pg_query-0.19.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for pg_query-0.19.tar.gz
Algorithm Hash digest
SHA256 f1e50c741122fe7f9b23f75d2a0f76edf776a7084156be3833cc6f38fd1c2d7c
MD5 c978da74b24a388e94fe22a5ec3afdb5
BLAKE2b-256 f5944d96da8dc87ecba4d5066a1a6e0383277999503081b1e7caef4ebbef24a4

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp36-cp36m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp36-cp36m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 e4102aaf6b3a77005340b267773c8102207246093cb7dcf4088dbdd4e96761ea
MD5 fcff13be8070d80967126d5d0a7b1b23
BLAKE2b-256 7ad289092fc1b53a177b2c46be1360208aa2db50fca07b651030886552aa4a69

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp36-cp36m-manylinux1_i686.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp36-cp36m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 9c09433a98c94bc6b98d30a0fa88497eed608dbc381acbe57d94bcf2a459069d
MD5 58bdb79c153e1afb397e0541935291e7
BLAKE2b-256 303a3c885abdbddd491b01fdb88eb7eba8673f808c77d0d3f3fcbc413665f8e6

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp35-cp35m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp35-cp35m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 e28c8f566d8833922cee583a4a3989609abef8eb76dc1f55af69598e42bf2a12
MD5 d35482d324ebdf6c0d762c45e76fe55b
BLAKE2b-256 46f3d27903e497191c7f6a3fc5cc98ac69a8dcce8e6ed0fa61933dbaf34d53b4

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp35-cp35m-manylinux1_i686.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp35-cp35m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 3b358ee6c573109848f45a4f1e33514c37805a53a9b710a6a5ae3aaf1df3d3a9
MD5 e51962884534571b583d3949604822c7
BLAKE2b-256 c49a33f9f4b37e6bea4ec9d16a9a4ea35341ce597981c7b5dc1cec8b5ba3b1d1

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp34-cp34m-manylinux1_x86_64.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp34-cp34m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 e475c58ea8933af6e19fa59910861e6f251f41490dbecadedfa4253962a2e557
MD5 6305a650ad5c0227aa089d7bf0f5c497
BLAKE2b-256 763e32139e2b87c2dd58ca7edd9a4bc6aa53138761cb0738b54bf316d5a40d90

See more details on using hashes here.

File details

Details for the file pg_query-0.19-cp34-cp34m-manylinux1_i686.whl.

File metadata

File hashes

Hashes for pg_query-0.19-cp34-cp34m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 3b6a8d504526876835dccf771fe2e3b6c14c426e584c028e714950b5078eca1f
MD5 da660b3dcf0a15af81418f04ef1f31e5
BLAKE2b-256 d3ddbb563054ebff879992c9e56a87825cdcecb29fc8b96946d907971f4d4611

See more details on using hashes here.

Release history Release notifications | RSS feed

0.29

1 file

0.28

7 files

0.27

7 files

0.26

7 files

0.25

7 files

0.24

7 files

0.23

7 files

0.22

7 files

0.21

7 files

0.20

7 files

This release

0.19 This release

7 files

0.18

7 files

0.17

7 files

0.16

7 files

0.15

7 files

0.14

7 files

0.13

7 files

0.12

7 files

0.11

7 files

0.10

7 files

0.9

3 files

0.8

3 files

0.7

3 files

0.6

3 files

0.5

3 files

0.4

3 files

0.3

3 files

0.2

1 file

0.1

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page