diff --git a/Cargo.lock b/Cargo.lock index 149ecdafe..96d5f578d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,6 +1499,7 @@ dependencies = [ "serde_json", "sha2", "sqlquack-syntax", + "sqlquack-syntax-postgres", "tempfile", "toml", ] @@ -1512,6 +1513,14 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "sqlquack-syntax-postgres" +version = "0.2.0" +dependencies = [ + "serde_json", + "sqlquack-syntax", +] + [[package]] name = "stacker" version = "0.1.25" diff --git a/Cargo.toml b/Cargo.toml index b9691a176..fead36671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/sqlquack", "crates/sqlquack-polyglot-check", "crates/sqlquack-syntax-coverage", + "crates/sqlquack-syntax-postgres", "crates/sqlquack-python", ] exclude = ["fuzz"] @@ -62,6 +63,7 @@ sqlquack-semantics = { path = "crates/sqlquack-semantics" } sqlquack-snowflake = { path = "crates/sqlquack-snowflake" } sqlquack-syntax = { path = "crates/sqlquack-syntax" } sqlquack-syntax-derive = { path = "crates/sqlquack-syntax-derive" } +sqlquack-syntax-postgres = { path = "crates/sqlquack-syntax-postgres" } stacker = "0.1" syn = { version = "2", features = ["full"] } tempfile = "3" diff --git a/coverage/syntax-postgres/README.md b/coverage/syntax-postgres/README.md new file mode 100644 index 000000000..763857eb2 --- /dev/null +++ b/coverage/syntax-postgres/README.md @@ -0,0 +1,172 @@ +# PostgreSQL syntax catalogue + +The denominator of SQLQuack's PostgreSQL syntax coverage: every documented PostgreSQL 18.6 syntax +form, one record per form, with a stable id `pg..[.]`. The layout and the +record formats are those of `coverage/syntax` (see `docs/syntax-coverage.md`); this file records +what differs. + +## Source + +The pinned source is the release tarball `postgresql-18.6.tar.bz2` (sha256 in `indexes.toml`), +not the rendered website. Its DocBook SGML is what postgresql.org/docs/18 is built from, so a +source id `doc.` names the page `https://www.postgresql.org/docs/18/.html`. +`indexes.toml` also binds the files the page list is read from (`reference.sgml`, +`ref/allfiles.sgml`, `filelist.sgml`, `postgres.sgml`) and the grammar the omission audit reads +(`src/backend/parser/gram.y`, `src/include/parser/kwlist.h`). + +A page is either + +- a SQL command reference page (a `refentry` of the SQL Commands part, 183 pages), or +- a `sect1` (or a chapter's introduction) of the SQL-language chapters: syntax, ddl, dml, + queries, datatype, json, array, rowtypes, rangetypes, func, typeconv, indices, textsearch, mvcc, + perform and parallel. + +`page_sha256` is the sha256 of that SGML text; `syntax_sha256` of its synopsis blocks and +signatures. + +## Regenerating + +```sh +python -m scripts.fetch_syntax_docs_pg CACHE # private cache, never committed +python -m scripts.build_syntax_catalogue_pg --cache CACHE --out coverage/syntax-postgres +``` + +The build writes nothing while any input is unresolved: a synopsis block with no disposition, a +disposition naming no block, a restatement of an uncatalogued form, or a hand form whose page +changed. PostgreSQL 19 is an in-place rebuild against its tarball; ids follow the same keeping +rules as the Snowflake catalogue (`scripts/syntax_catalogue/_helpers/forms.py`, reused by import). + +## Omission audit (`audit.toml`) + +```sh +python -m scripts.audit_syntax_catalogue_pg --cache CACHE +``` + +An independent check against the server's own grammar, `gram.y` and `kwlist.h` of the pinned +release. It reads every rule of gram.y and spells each alternative of every rule reachable from +`toplevel_stmt`, `stmt` and the expression rules (720 of 733 rules, with no depth bound) as keyword +sequences, +expanding nonterminals that are keyword sets (`DROP object_type_any_name` checks DROP TABLE, +DROP SEQUENCE and so on). A sequence is covered when some catalogued form, with its page's +definitions inlined, contains its keywords in order; for a statement the form's leading required +keywords must also appear in the sequence, so DROP FOREIGN TABLE does not cover DROP TABLE. +Every kwlist.h keyword that a production uses must appear in some grammar. + +`audit.toml` binds the sha256 of gram.y and kwlist.h and gives a reviewed reason for every +production that no form covers (`[[production]]`, keyed by rule and production text) and every +keyword that no grammar names (`[[keyword]]`). The audit fails on an unreviewed gap, a review +naming no current gap, or a changed grammar file. The reasons are of three kinds: syntax gram.y +accepts that the 18.6 documentation does not document (the spelling ANALYSE, GLOBAL and LOCAL +before TEMPORARY outside CREATE TABLE, `BETWEEN ASYMMETRIC`, `TREAT`, `NCHAR`); productions that +only raise an error (CREATE ASSERTION, `ALTER TYPE ... DROP VALUE`, UNENCRYPTED PASSWORD); and +documented values of a placeholder (`NFC`, `OUT`, `SETOF`). Single-keyword alternatives below a +statement are checked by the keyword rule only. + +`[[unwalked]]` lists each of the 13 rules the walk does not reach with its reason: the start +symbol and top-level wrappers, the PL/pgSQL-only parse modes and the five keyword category lists. +The audit fails on an unwalked rule that is not listed. `limits` states what the method does not +check, such as identifiers, punctuation and adjacency of keywords. + +## Form rules + +1. **Command pages.** The `` synopsis is split into statements at each unindented + line that starts with the command's first word (SELECT also `WITH` and `TABLE`). Each + statement is a form. `where X is:` (and `and X is`, `X in ... is`) starts a + sub-grammar definition, `[[definition]]` `doc.::`; `can be one of` / `is one of` + lists its alternatives one per line. +2. **Actions.** A statement whose grammar takes ` [, ... ]`, where the page lists + `action` as alternatives (ALTER TABLE and six other ALTER pages), becomes one form per action, with the + action substituted into the statement. +3. **Other synopsis blocks** (on command pages outside the Synopsis, and in the chapters) each + have a reviewed disposition in `hand.toml` `[[block]]`, keyed by page and block sha256: + `form` (each production a form; on a command page, older syntax still accepted, named + `.