Skip to content

Reference manual

Introduction

Quick reference for the pyadql command:

pyadql [-h] [-v] [--level {INFO,DEBUG,WARNING,ERROR,CRITICAL,TRACE}]
       [-f PATH] [--tree] [--json] [-q] [query]
Option Effect
query (positional, optional) ADQL query to parse; if omitted, read from -f or standard input
-f, --file PATH Read the ADQL query from a file
--tree Print the raw Lark parse tree instead of the typed AST
--json Print the AST as JSON (each node carries a "_type" key) instead of a Python repr
--level {INFO,DEBUG,WARNING,ERROR,CRITICAL,TRACE} Set the logging verbosity (default: ERROR)
-q, --quiet Only print errors (overrides --level)
-v, --version Print the installed pyadql version and exit
-h, --help Print the full help text and exit

For the corresponding Python API reference, see the auto-generated API documentation on this site (built from the docstrings in src/pyadql/ via mkdocstrings) and docs/AST.md for a guided tour of the AST node types.

Help method

  • pyadql --help prints the full option list with a short description of each (generated by argparse from the definitions in src/pyadql/__main__.py).
  • pyadql --version (or -v) prints the installed version string.
  • Installation instructions are in operation_manual.md; if installation itself fails, the error comes from pip/uv directly (missing Python version, network issue, ...), not from PyADQL.
  • Parsing failures are diagnosed by re-running with --level DEBUG or --level TRACE; see "Error messages" below and operation_manual.md.

Screen definitions and operations

Not applicable: PyADQL has no graphical interface. It is a text-in / text-out command-line tool. Input is the ADQL query (as a CLI argument, a file via -f, or standard input); output is the parsed AST on stdout and diagnostic logs on stderr, kept on separate streams so they can be redirected independently.

Commands and operations

The pyadql command is the package's only executable entry point (declared under [project.scripts] in pyproject.toml). Its operations are fully described in operation_manual.md; the equivalent Python API is pyadql.parse_adql(query) (query text -> typed AST) and pyadql.parse_tree(query) (query text -> raw Lark tree, mainly for debugging the grammar itself).

ADQL language coverage

What's supported by the grammar (src/pyadql/grammar/*.lark, transcribed from Annex A of IVOA Recommendation ADQL 2.1):

  • SELECT (DISTINCT, TOP n, *, column aliases)
  • WITH (common table expressions, ADQL 2.1)
  • FROM with every join type (INNER, LEFT/RIGHT/FULL [OUTER], NATURAL, ON, USING), derived tables (SELECT ...) AS alias
  • WHERE, GROUP BY, HAVING
  • ORDER BY [ASC|DESC], OFFSET (ADQL 2.1) -- applying to the whole combined query, per Annex A (see ast_nodes.SelectExpression)
  • Set operators UNION / EXCEPT / INTERSECT (+ ALL)
  • Predicates: comparisons, BETWEEN, IN (list or subquery), LIKE and ILIKE (case-insensitive, ADQL 2.1), IS [NOT] NULL, EXISTS
  • Arithmetic expressions (+ - * /), || concatenation, NOT/AND/OR
  • Scalar and correlated subqueries
  • CAST(expr AS type) with ADQL 2.1's enumerated target types (CHAR/ VARCHAR(n), SMALLINT/INTEGER/BIGINT/REAL/DOUBLE PRECISION, TIMESTAMP, POINT/CIRCLE/POLYGON)
  • COALESCE(a, b, ...) (ADQL 2.1)
  • Aggregate functions: COUNT, SUM, AVG, MIN, MAX (+ DISTINCT)
  • ADQL numeric functions: ABS, CEILING, FLOOR, ROUND, SQRT, POWER, MOD, LOG, LOG10, EXP, PI, RAND, SIGN, TRUNCATE, trigonometric functions (SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, COT), DEGREES, RADIANS, IN_UNIT (ADQL 2.1)
  • String functions: LOWER, UPPER
  • ADQL geometry: POINT (optional coordsys, ADQL 2.1), CIRCLE, BOX, POLYGON (all three accepting either a raw ra, dec pair or a ready-made point-valued expression as center/vertex, ADQL 2.1), REGION, CONTAINS, INTERSECTS, AREA, CENTROID, COORD1, COORD2, COORDSYS, DISTANCE (both the two-point and the 2.1-added 4-number overload)
  • User-defined functions (any unrecognized name(...) call is captured as UserFunctionCall, to cover TAP-service-specific extensions)
  • Double-quoted identifiers ("My Column", with ""-escaped literal quotes), -- comments

Known limitations (feel free to extend, see grammar/core.lark):

  • TIMESTAMP/INTERVAL literals (as opposed to CAST(... AS TIMESTAMP), which is supported) are not yet in the grammar; Annex A itself barely defines them.
  • No specific handling of TAP_UPLOAD (a TAP-protocol detail, outside the ADQL grammar proper).
  • Earley's ambiguity resolution (ambiguity='resolve') picks the "most likely" derivation; for very unusual queries, double-check the resulting AST. One concrete case: POINT(a, b, c) where none of a/b/c fit POINT's expected shape (e.g. a isn't a string literal coordsys) falls back to a generic UserFunctionCall named "POINT" rather than being rejected outright -- a harmless permissive superset, but worth knowing about if you're validating strict spec compliance rather than just parsing.

To cover a missing case: add the rule in the relevant src/pyadql/grammar/*.lark file (following Annex A, see the [AnnexA #x] cross-references already there), then add the matching method in ADQLTransformer (src/pyadql/parser.py) that builds the AST node (src/pyadql/ast_nodes.py).

Validation against a real production log

The grammar was run against ~2584 unique ADQL queries extracted from a production log of a TAP service (DaCHS, EPN-TAP): 2533 parsed successfully, 51 fail, and break down as:

  • LIMIT (31 queries): generic SQL syntax never standardized in ADQL (the standard uses TOP) -- correctly rejected, a standard-compliant TAP service rejects it too,
  • one SQL injection attempt by a bot (correctly rejected),
  • truncated log lines or client queries that are plainly malformed (missing commas, unclosed quotes).

Error messages

PyADQL reports failures through two channels:

  1. Exit code (CLI) / exception (library) -- see the table in operation_manual.md: 0 success, 1 ADQL parsing failure, 2 usage error.
  2. A loguru-formatted error message on stderr, always shown regardless of --level, explicitly tagged as either:
  3. a grammar error -- the query does not conform to ADQL 2.1's Annex A grammar (a real syntax mistake, or unsupported syntax, see "Known limitations" above),
  4. a transformer error -- the grammar accepted the query but ADQLTransformer could not build the AST for it (report this as a bug, see CONTRIBUTING.md).

Re-running the failing command with --level DEBUG adds per-step timing and an AST summary on success, or the point of failure with more context on error; --level TRACE additionally prints the raw Lark parse tree, which is the most direct way to see exactly how far the grammar got before failing.