From 8350bddb2a16213137a50c1f09862278c92cdb26 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 21 Aug 2025 09:25:47 +0200 Subject: [PATCH 01/15] i guess i will try this --- Cargo.lock | 20 + Cargo.toml | 2 + crates/pgt_pretty_print/Cargo.toml | 17 + crates/pgt_pretty_print/src/codegen/mod.rs | 1 + .../src/codegen/token_kind.rs | 1 + crates/pgt_pretty_print/src/lib.rs | 71 +++ crates/pgt_pretty_print_codegen/Cargo.toml | 25 + crates/pgt_pretty_print_codegen/README.md | 1 + crates/pgt_pretty_print_codegen/build.rs | 49 ++ .../postgres/17-6.1.0/kwlist.h | 518 ++++++++++++++++++ .../pgt_pretty_print_codegen/src/keywords.rs | 43 ++ crates/pgt_pretty_print_codegen/src/lib.rs | 9 + .../src/token_kind.rs | 120 ++++ 13 files changed, 877 insertions(+) create mode 100644 crates/pgt_pretty_print/Cargo.toml create mode 100644 crates/pgt_pretty_print/src/codegen/mod.rs create mode 100644 crates/pgt_pretty_print/src/codegen/token_kind.rs create mode 100644 crates/pgt_pretty_print/src/lib.rs create mode 100644 crates/pgt_pretty_print_codegen/Cargo.toml create mode 100644 crates/pgt_pretty_print_codegen/README.md create mode 100644 crates/pgt_pretty_print_codegen/build.rs create mode 100644 crates/pgt_pretty_print_codegen/postgres/17-6.1.0/kwlist.h create mode 100644 crates/pgt_pretty_print_codegen/src/keywords.rs create mode 100644 crates/pgt_pretty_print_codegen/src/lib.rs create mode 100644 crates/pgt_pretty_print_codegen/src/token_kind.rs diff --git a/Cargo.lock b/Cargo.lock index be7eacb7..7315db1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3023,6 +3023,26 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "pgt_pretty_print" +version = "0.0.0" +dependencies = [ + "pgt_pretty_print_codegen", +] + +[[package]] +name = "pgt_pretty_print_codegen" +version = "0.0.0" +dependencies = [ + "anyhow", + "convert_case", + "proc-macro2", + "prost-reflect", + "protox", + "quote", + "ureq", +] + [[package]] name = "pgt_query" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 84fed3d1..841f3c86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,8 @@ pgt_lexer_codegen = { path = "./crates/pgt_lexer_codegen", version = "0 pgt_lsp = { path = "./crates/pgt_lsp", version = "0.0.0" } pgt_markup = { path = "./crates/pgt_markup", version = "0.0.0" } pgt_plpgsql_check = { path = "./crates/pgt_plpgsql_check", version = "0.0.0" } +pgt_pretty_print = { path = "./crates/pgt_pretty_print", version = "0.0.0" } +pgt_pretty_print_codegen = { path = "./crates/pgt_pretty_print_codegen", version = "0.0.0" } pgt_query = { path = "./crates/pgt_query", version = "0.0.0" } pgt_query_ext = { path = "./crates/pgt_query_ext", version = "0.0.0" } pgt_query_macros = { path = "./crates/pgt_query_macros", version = "0.0.0" } diff --git a/crates/pgt_pretty_print/Cargo.toml b/crates/pgt_pretty_print/Cargo.toml new file mode 100644 index 00000000..36d244a5 --- /dev/null +++ b/crates/pgt_pretty_print/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pgt_pretty_print" +repository.workspace = true +version = "0.0.0" + + +[dependencies] +pgt_pretty_print_codegen.workspace = true + +[dev-dependencies] diff --git a/crates/pgt_pretty_print/src/codegen/mod.rs b/crates/pgt_pretty_print/src/codegen/mod.rs new file mode 100644 index 00000000..92f817a0 --- /dev/null +++ b/crates/pgt_pretty_print/src/codegen/mod.rs @@ -0,0 +1 @@ +pub mod token_kind; diff --git a/crates/pgt_pretty_print/src/codegen/token_kind.rs b/crates/pgt_pretty_print/src/codegen/token_kind.rs new file mode 100644 index 00000000..17aefbf5 --- /dev/null +++ b/crates/pgt_pretty_print/src/codegen/token_kind.rs @@ -0,0 +1 @@ +pgt_pretty_print_codegen::token_kind_codegen!(); diff --git a/crates/pgt_pretty_print/src/lib.rs b/crates/pgt_pretty_print/src/lib.rs new file mode 100644 index 00000000..63811bc4 --- /dev/null +++ b/crates/pgt_pretty_print/src/lib.rs @@ -0,0 +1,71 @@ +mod codegen; + +pub use crate::codegen::token_kind::TokenKind; + +use std::collections::VecDeque; + +#[derive(Debug, Clone, PartialEq)] +pub enum LineType { + /// Must break (semicolon, etc.) + Hard, + /// Break if group doesn't fit + Soft, + /// Break if group doesn't fit, but collapse to space if it does + SoftOrSpace, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum LayoutEvent { + Token(TokenKind), + Space, + Line(LineType), + GroupStart { + id: Option, + break_parent: bool, + }, + GroupEnd, + IndentStart, + IndentEnd, +} + +pub struct EventEmitter { + pub events: VecDeque, +} + +impl EventEmitter { + pub fn new() -> Self { + Self { + events: VecDeque::new(), + } + } + + /// Helper methods for emitting events + pub fn token(&mut self, token: TokenKind) { + self.events.push_back(LayoutEvent::Token(token)); + } + + pub fn space(&mut self) { + self.events.push_back(LayoutEvent::Space); + } + + pub fn line(&mut self, line_type: LineType) { + self.events.push_back(LayoutEvent::Line(line_type)); + } + + pub fn group_start(&mut self, id: Option, break_parent: bool) { + self.events + .push_back(LayoutEvent::GroupStart { id, break_parent }); + } + + pub fn group_end(&mut self) { + self.events.push_back(LayoutEvent::GroupEnd); + } + + pub fn indent_start(&mut self) { + self.events.push_back(LayoutEvent::IndentStart); + } + + pub fn indent_end(&mut self) { + self.events.push_back(LayoutEvent::IndentEnd); + } +} diff --git a/crates/pgt_pretty_print_codegen/Cargo.toml b/crates/pgt_pretty_print_codegen/Cargo.toml new file mode 100644 index 00000000..79b7aeec --- /dev/null +++ b/crates/pgt_pretty_print_codegen/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors.workspace = true +categories.workspace = true +description = "" +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +name = "pgt_pretty_print_codegen" +repository.workspace = true +version = "0.0.0" + +[dependencies] +anyhow = { workspace = true } +convert_case = { workspace = true } +proc-macro2.workspace = true +prost-reflect = { workspace = true } +protox = { workspace = true } +quote.workspace = true + +[build-dependencies] +ureq = "2.9" + +[lib] +proc-macro = true diff --git a/crates/pgt_pretty_print_codegen/README.md b/crates/pgt_pretty_print_codegen/README.md new file mode 100644 index 00000000..57bdaa34 --- /dev/null +++ b/crates/pgt_pretty_print_codegen/README.md @@ -0,0 +1 @@ +Heavily inspired by and copied from [squawk_parser](https://github.com/sbdchd/squawk/tree/9acfecbbb7f3c7eedcbaf060e7b25f9afa136db3/crates/squawk_parser). Thanks for making all the hard work MIT-licensed! diff --git a/crates/pgt_pretty_print_codegen/build.rs b/crates/pgt_pretty_print_codegen/build.rs new file mode 100644 index 00000000..70c9635d --- /dev/null +++ b/crates/pgt_pretty_print_codegen/build.rs @@ -0,0 +1,49 @@ +use std::env; +use std::fs; +use std::io::Write; +use std::path::PathBuf; + +// TODO make this selectable via feature flags +static LIBPG_QUERY_TAG: &str = "17-6.1.0"; + +/// Downloads the `kwlist.h` file from the specified version of `libpg_query` +fn main() -> Result<(), Box> { + let version = LIBPG_QUERY_TAG.to_string(); + + // Check for the postgres header file in the source tree first + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); + let headers_dir = manifest_dir.join("postgres").join(&version); + let kwlist_path = headers_dir.join("kwlist.h"); + + // Only download if the file doesn't exist + if !kwlist_path.exists() { + println!( + "cargo:warning=Downloading kwlist.h for libpg_query {}", + version + ); + + fs::create_dir_all(&headers_dir)?; + + let proto_url = format!( + "https://raw.githubusercontent.com/pganalyze/libpg_query/{}/src/postgres/include/parser/kwlist.h", + version + ); + + let response = ureq::get(&proto_url).call()?; + let content = response.into_string()?; + + let mut file = fs::File::create(&kwlist_path)?; + file.write_all(content.as_bytes())?; + + println!("cargo:warning=Successfully downloaded kwlist.h"); + } + + println!( + "cargo:rustc-env=PG_QUERY_KWLIST_PATH={}", + kwlist_path.display() + ); + + println!("cargo:rerun-if-changed={}", kwlist_path.display()); + + Ok(()) +} diff --git a/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/kwlist.h b/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/kwlist.h new file mode 100644 index 00000000..658d7ff6 --- /dev/null +++ b/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/kwlist.h @@ -0,0 +1,518 @@ +/*------------------------------------------------------------------------- + * + * kwlist.h + * + * The keyword lists are kept in their own source files for use by + * automatic tools. The exact representation of a keyword is determined + * by the PG_KEYWORD macro, which is not defined in this file; it can + * be defined by the caller for special purposes. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/include/parser/kwlist.h + * + *------------------------------------------------------------------------- + */ + +/* there is deliberately not an #ifndef KWLIST_H here */ + +/* + * List of keyword (name, token-value, category, bare-label-status) entries. + * + * Note: gen_keywordlist.pl requires the entries to appear in ASCII order. + */ + +/* name, value, category, is-bare-label */ +PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("aggregate", AGGREGATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("all", ALL, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("also", ALSO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("alter", ALTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("always", ALWAYS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("analyse", ANALYSE, RESERVED_KEYWORD, BARE_LABEL) /* British spelling */ +PG_KEYWORD("analyze", ANALYZE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("and", AND, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("any", ANY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("array", ARRAY, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("as", AS, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("asc", ASC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("asensitive", ASENSITIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("both", BOTH, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("breadth", BREADTH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("by", BY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cache", CACHE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("call", CALL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("called", CALLED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cascade", CASCADE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cascaded", CASCADED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("case", CASE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cast", CAST, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("catalog", CATALOG_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("chain", CHAIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("char", CHAR_P, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("character", CHARACTER, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("characteristics", CHARACTERISTICS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("check", CHECK, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("checkpoint", CHECKPOINT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("class", CLASS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("close", CLOSE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cluster", CLUSTER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("collation", COLLATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("columns", COLUMNS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("compression", COMPRESSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("content", CONTENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("continue", CONTINUE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("conversion", CONVERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("create", CREATE, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_role", CURRENT_ROLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_schema", CURRENT_SCHEMA, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("day", DAY_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("deallocate", DEALLOCATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dec", DEC, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("decimal", DECIMAL_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("declare", DECLARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("default", DEFAULT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("defaults", DEFAULTS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("deferrable", DEFERRABLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("deferred", DEFERRED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("definer", DEFINER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("depth", DEPTH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("distinct", DISTINCT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("do", DO, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("finalize", FINALIZE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("float", FLOAT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("from", FROM, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("groups", GROUPS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("having", HAVING, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("indent", INDENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inline", INLINE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("inner", INNER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("inout", INOUT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("input", INPUT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("insensitive", INSENSITIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("insert", INSERT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("instead", INSTEAD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("int", INT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("into", INTO, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("lateral", LATERAL_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("matched", MATCHED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("merge", MERGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("merge_action", MERGE_ACTION, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("nested", NESTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfc", NFC, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("not", NOT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nothing", NOTHING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("notify", NOTIFY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("notnull", NOTNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("nowait", NOWAIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("null", NULL_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("of", OF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("on", ON, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("only", ONLY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("or", OR, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("order", ORDER, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("overlay", OVERLAY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD, AS_LABEL) +PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("primary", PRIMARY, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("read", READ, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("real", REAL, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("reassign", REASSIGN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("ref", REF_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("routine", ROUTINE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("routines", ROUTINES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("security", SECURITY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("select", SELECT, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sequence", SEQUENCE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sequences", SEQUENCES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("serializable", SERIALIZABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("server", SERVER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("set", SET, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("simple", SIMPLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("source", SOURCE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("start", START, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("statement", STATEMENT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("string", STRING_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("tablespace", TABLESPACE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("target", TARGET, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("then", THEN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("ties", TIES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("time", TIME, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("to", TO, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("trailing", TRAILING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("trusted", TRUSTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unknown", UNKNOWN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unlisten", UNLISTEN, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unlogged", UNLOGGED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("until", UNTIL, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("update", UPDATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("user", USER, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("using", USING, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("when", WHEN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("where", WHERE, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("whitespace", WHITESPACE_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("window", WINDOW, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("with", WITH, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("within", WITHIN, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("without", WITHOUT, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("work", WORK, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("wrapper", WRAPPER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("write", WRITE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("xml", XML_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlattributes", XMLATTRIBUTES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlconcat", XMLCONCAT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlelement", XMLELEMENT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlexists", XMLEXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlforest", XMLFOREST, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlnamespaces", XMLNAMESPACES, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlparse", XMLPARSE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlpi", XMLPI, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlroot", XMLROOT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmlserialize", XMLSERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("xmltable", XMLTABLE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("year", YEAR_P, UNRESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("yes", YES_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("zone", ZONE, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/crates/pgt_pretty_print_codegen/src/keywords.rs b/crates/pgt_pretty_print_codegen/src/keywords.rs new file mode 100644 index 00000000..f0104c8d --- /dev/null +++ b/crates/pgt_pretty_print_codegen/src/keywords.rs @@ -0,0 +1,43 @@ +// from https://github.com/sbdchd/squawk/blob/ac9f90c3b2be8d2c46fd5454eb48975afd268dbe/crates/xtask/src/keywords.rs +use anyhow::{Context, Ok, Result}; +use std::path; + +fn parse_header() -> Result> { + // use the environment variable set by the build script to locate the kwlist.h file + let kwlist_file = path::PathBuf::from(env!("PG_QUERY_KWLIST_PATH")); + let data = std::fs::read_to_string(kwlist_file).context("Failed to read kwlist.h")?; + + let mut keywords = Vec::new(); + + for line in data.lines() { + if line.starts_with("PG_KEYWORD") { + let line = line + .split(&['(', ')']) + .nth(1) + .context("Invalid kwlist.h structure")?; + + let row_items: Vec<&str> = line.split(',').collect(); + + match row_items[..] { + [name, _value, _category, _is_bare_label] => { + let name = name.trim().replace('\"', ""); + keywords.push(name); + } + _ => anyhow::bail!("Problem reading kwlist.h row"), + } + } + } + + Ok(keywords) +} + +pub(crate) struct KeywordKinds { + pub(crate) all_keywords: Vec, +} + +pub(crate) fn keyword_kinds() -> Result { + let mut all_keywords = parse_header()?; + all_keywords.sort(); + + Ok(KeywordKinds { all_keywords }) +} diff --git a/crates/pgt_pretty_print_codegen/src/lib.rs b/crates/pgt_pretty_print_codegen/src/lib.rs new file mode 100644 index 00000000..b8f3594e --- /dev/null +++ b/crates/pgt_pretty_print_codegen/src/lib.rs @@ -0,0 +1,9 @@ +mod keywords; +mod token_kind; + +use token_kind::token_kind_mod; + +#[proc_macro] +pub fn token_kind_codegen(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { + token_kind_mod().into() +} diff --git a/crates/pgt_pretty_print_codegen/src/token_kind.rs b/crates/pgt_pretty_print_codegen/src/token_kind.rs new file mode 100644 index 00000000..e8a5ee06 --- /dev/null +++ b/crates/pgt_pretty_print_codegen/src/token_kind.rs @@ -0,0 +1,120 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; + +use crate::keywords::{KeywordKinds, keyword_kinds}; + +const STRUCTURAL_PUNCT: &[(&str, &str)] = &[ + (";", "SEMICOLON"), // Statement terminator - structural + (",", "COMMA"), // List separator - structural + ("(", "L_PAREN"), // Grouping - structural + (")", "R_PAREN"), // Grouping - structural + ("[", "L_BRACK"), // Array indexing - structural + ("]", "R_BRACK"), // Array indexing - structural + (".", "DOT"), // Qualified names (schema.table) - structural +]; + +const PUNCT: &[(&str, &str)] = &[ + ("$", "DOLLAR"), // Positional parameters ($1, $2) - special parsing + ("::", "DOUBLE_COLON"), // Type cast operator - special syntax +]; + +const EXTRA: &[&str] = &["POSITIONAL_PARAM", "COMMENT"]; + +const LITERALS: &[&str] = &[ + "BIT_STRING", + "BYTE_STRING", + "DOLLAR_QUOTED_STRING", + "ESC_STRING", + "FLOAT_NUMBER", + "INT_NUMBER", + "NULL", + "STRING", + "IDENT", +]; + +const VARIANT_DATA: &[(&str, &str)] = &[ + ("String", "String"), + ("EscString", "String"), // E'hello\nworld' + ("DollarQuotedString", "String"), // $$hello world$$ + ("IntNumber", "i64"), // 123, -456 + ("FloatNumber", "f64"), // 123.45, 1.2e-3 + ("BitString", "String"), // B'1010', X'FF' + ("ByteString", "String"), // Similar to bit string + ("Ident", "String"), // user_id, table_name + ("PositionalParam", "u32"), // $1, $2, $3 (the number matters!) + ("Comment", "String"), // /* comment text */ +]; + +pub fn token_kind_mod() -> proc_macro2::TokenStream { + let keywords = keyword_kinds().expect("Failed to get keyword kinds"); + + let KeywordKinds { all_keywords, .. } = keywords; + + let mut enum_variants: Vec = Vec::new(); + let mut from_kw_match_arms: Vec = Vec::new(); + + // helper function to create a variant quote for enum + // used to handle variants with data types + let variant_quote = |name: &str| { + let variant_name = format_ident!("{}", name); + + if let Some((_, data_type)) = VARIANT_DATA.iter().find(|&&(n, _)| n == name) { + let data_type = format_ident!("{}", data_type); + quote! { #variant_name(#data_type) } + } else { + quote! { #variant_name } + } + }; + + // collect keywords + for kw in &all_keywords { + if kw.to_uppercase().contains("WHITESPACE") { + continue; // Skip whitespace as it is handled separately + } + + let kind_ident = format_ident!("{}_KW", kw.to_case(Case::UpperSnake)); + + enum_variants.push(quote! { #kind_ident }); + from_kw_match_arms.push(quote! { + #kw => Some(SyntaxKind::#kind_ident) + }); + } + + // collect extra keywords + EXTRA.iter().for_each(|&name| { + enum_variants.push(variant_quote(name)); + }); + + // collect punctuations + STRUCTURAL_PUNCT.iter().for_each(|&(_ascii_name, variant)| { + let variant_name = format_ident!("{}", variant); + enum_variants.push(quote! { #variant_name }); + }); + PUNCT.iter().for_each(|&(_ascii_name, variant)| { + let variant_name = format_ident!("{}", variant); + enum_variants.push(quote! { #variant_name }); + }); + + // collect literals + LITERALS.iter().for_each(|&name| { + enum_variants.push(variant_quote(name)); + }); + + quote! { + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub enum TokenKind { + #(#enum_variants),*, + } + + impl TokenKind { + pub(crate) fn from_keyword(ident: &str) -> Option { + let lower_ident = ident.to_ascii_lowercase(); + match lower_ident.as_str() { + #(#from_kw_match_arms),*, + _ => None + } + } + } + } +} From 9e039b7ea7f5b2ebd75bb616919cd9e1fbe6ffab Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 21 Aug 2025 09:30:05 +0200 Subject: [PATCH 02/15] progress --- crates/pgt_pretty_print/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/pgt_pretty_print/src/lib.rs b/crates/pgt_pretty_print/src/lib.rs index 63811bc4..e36c1310 100644 --- a/crates/pgt_pretty_print/src/lib.rs +++ b/crates/pgt_pretty_print/src/lib.rs @@ -39,7 +39,6 @@ impl EventEmitter { } } - /// Helper methods for emitting events pub fn token(&mut self, token: TokenKind) { self.events.push_back(LayoutEvent::Token(token)); } From 048db8248ecaf3693ddd0abb2aad701e3cb5e162 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 21 Aug 2025 09:33:13 +0200 Subject: [PATCH 03/15] progress --- crates/pgt_pretty_print/src/lib.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/pgt_pretty_print/src/lib.rs b/crates/pgt_pretty_print/src/lib.rs index e36c1310..68a25062 100644 --- a/crates/pgt_pretty_print/src/lib.rs +++ b/crates/pgt_pretty_print/src/lib.rs @@ -2,8 +2,6 @@ mod codegen; pub use crate::codegen::token_kind::TokenKind; -use std::collections::VecDeque; - #[derive(Debug, Clone, PartialEq)] pub enum LineType { /// Must break (semicolon, etc.) @@ -29,42 +27,40 @@ pub enum LayoutEvent { } pub struct EventEmitter { - pub events: VecDeque, + pub events: Vec, } impl EventEmitter { pub fn new() -> Self { - Self { - events: VecDeque::new(), - } + Self { events: Vec::new() } } pub fn token(&mut self, token: TokenKind) { - self.events.push_back(LayoutEvent::Token(token)); + self.events.push(LayoutEvent::Token(token)); } pub fn space(&mut self) { - self.events.push_back(LayoutEvent::Space); + self.events.push(LayoutEvent::Space); } pub fn line(&mut self, line_type: LineType) { - self.events.push_back(LayoutEvent::Line(line_type)); + self.events.push(LayoutEvent::Line(line_type)); } pub fn group_start(&mut self, id: Option, break_parent: bool) { self.events - .push_back(LayoutEvent::GroupStart { id, break_parent }); + .push(LayoutEvent::GroupStart { id, break_parent }); } pub fn group_end(&mut self) { - self.events.push_back(LayoutEvent::GroupEnd); + self.events.push(LayoutEvent::GroupEnd); } pub fn indent_start(&mut self) { - self.events.push_back(LayoutEvent::IndentStart); + self.events.push(LayoutEvent::IndentStart); } pub fn indent_end(&mut self) { - self.events.push_back(LayoutEvent::IndentEnd); + self.events.push(LayoutEvent::IndentEnd); } } From 0911372bd66616e3d4f6c7bc49ae8f032509a346 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 21 Aug 2025 19:13:35 +0200 Subject: [PATCH 04/15] progress --- Cargo.lock | 1 + crates/pgt_pretty_print/Cargo.toml | 1 + crates/pgt_pretty_print/src/emitter.rs | 68 +++++++++++++++++++ crates/pgt_pretty_print/src/lib.rs | 65 +----------------- crates/pgt_pretty_print/src/nodes.rs | 37 ++++++++++ .../src/token_kind.rs | 2 +- 6 files changed, 110 insertions(+), 64 deletions(-) create mode 100644 crates/pgt_pretty_print/src/emitter.rs create mode 100644 crates/pgt_pretty_print/src/nodes.rs diff --git a/Cargo.lock b/Cargo.lock index 7315db1c..129e7e01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3028,6 +3028,7 @@ name = "pgt_pretty_print" version = "0.0.0" dependencies = [ "pgt_pretty_print_codegen", + "pgt_query", ] [[package]] diff --git a/crates/pgt_pretty_print/Cargo.toml b/crates/pgt_pretty_print/Cargo.toml index 36d244a5..d6772e6c 100644 --- a/crates/pgt_pretty_print/Cargo.toml +++ b/crates/pgt_pretty_print/Cargo.toml @@ -13,5 +13,6 @@ version = "0.0.0" [dependencies] pgt_pretty_print_codegen.workspace = true +pgt_query.workspace = true [dev-dependencies] diff --git a/crates/pgt_pretty_print/src/emitter.rs b/crates/pgt_pretty_print/src/emitter.rs new file mode 100644 index 00000000..f9833872 --- /dev/null +++ b/crates/pgt_pretty_print/src/emitter.rs @@ -0,0 +1,68 @@ +pub use crate::codegen::token_kind::TokenKind; + +#[derive(Debug, Clone, PartialEq)] +pub enum LineType { + /// Must break (semicolon, etc.) + Hard, + /// Break if group doesn't fit + Soft, + /// Break if group doesn't fit, but collapse to space if it does + SoftOrSpace, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum LayoutEvent { + Token(TokenKind), + Space, + Line(LineType), + GroupStart { + id: Option, + break_parent: bool, + }, + GroupEnd, + IndentStart, + IndentEnd, +} + +pub struct EventEmitter { + pub events: Vec, +} + +impl EventEmitter { + pub fn new() -> Self { + Self { events: Vec::new() } + } + + pub fn token(&mut self, token: TokenKind) { + self.events.push(LayoutEvent::Token(token)); + } + + pub fn space(&mut self) { + self.events.push(LayoutEvent::Space); + } + + pub fn line(&mut self, line_type: LineType) { + self.events.push(LayoutEvent::Line(line_type)); + } + + pub fn group_start(&mut self, id: Option, break_parent: bool) { + self.events + .push(LayoutEvent::GroupStart { id, break_parent }); + } + + pub fn group_end(&mut self) { + self.events.push(LayoutEvent::GroupEnd); + } + + pub fn indent_start(&mut self) { + self.events.push(LayoutEvent::IndentStart); + } + + pub fn indent_end(&mut self) { + self.events.push(LayoutEvent::IndentEnd); + } +} + +pub trait ToTokens { + fn to_tokens(&self, emitter: &mut EventEmitter); +} diff --git a/crates/pgt_pretty_print/src/lib.rs b/crates/pgt_pretty_print/src/lib.rs index 68a25062..93cfac23 100644 --- a/crates/pgt_pretty_print/src/lib.rs +++ b/crates/pgt_pretty_print/src/lib.rs @@ -1,66 +1,5 @@ mod codegen; +mod emitter; +mod nodes; pub use crate::codegen::token_kind::TokenKind; - -#[derive(Debug, Clone, PartialEq)] -pub enum LineType { - /// Must break (semicolon, etc.) - Hard, - /// Break if group doesn't fit - Soft, - /// Break if group doesn't fit, but collapse to space if it does - SoftOrSpace, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum LayoutEvent { - Token(TokenKind), - Space, - Line(LineType), - GroupStart { - id: Option, - break_parent: bool, - }, - GroupEnd, - IndentStart, - IndentEnd, -} - -pub struct EventEmitter { - pub events: Vec, -} - -impl EventEmitter { - pub fn new() -> Self { - Self { events: Vec::new() } - } - - pub fn token(&mut self, token: TokenKind) { - self.events.push(LayoutEvent::Token(token)); - } - - pub fn space(&mut self) { - self.events.push(LayoutEvent::Space); - } - - pub fn line(&mut self, line_type: LineType) { - self.events.push(LayoutEvent::Line(line_type)); - } - - pub fn group_start(&mut self, id: Option, break_parent: bool) { - self.events - .push(LayoutEvent::GroupStart { id, break_parent }); - } - - pub fn group_end(&mut self) { - self.events.push(LayoutEvent::GroupEnd); - } - - pub fn indent_start(&mut self) { - self.events.push(LayoutEvent::IndentStart); - } - - pub fn indent_end(&mut self) { - self.events.push(LayoutEvent::IndentEnd); - } -} diff --git a/crates/pgt_pretty_print/src/nodes.rs b/crates/pgt_pretty_print/src/nodes.rs new file mode 100644 index 00000000..374f55ed --- /dev/null +++ b/crates/pgt_pretty_print/src/nodes.rs @@ -0,0 +1,37 @@ +use crate::{ + TokenKind, + emitter::{EventEmitter, ToTokens}, +}; + +impl ToTokens for pgt_query::Node { + fn to_tokens(&self, e: &mut EventEmitter) { + if let Some(node) = &self.node { + node.to_tokens(e); + } + } +} + +impl ToTokens for pgt_query::protobuf::node::Node { + fn to_tokens(&self, e: &mut EventEmitter) { + match self { + pgt_query::protobuf::node::Node::SelectStmt(stmt) => stmt.as_ref().to_tokens(e), + _ => { + unimplemented!("Node type {:?} not implemented for to_tokens", self); + } + } + } +} + +impl ToTokens for pgt_query::protobuf::SelectStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(None, false); + e.token(TokenKind::SELECT_KW); + e.space(); + self.target_list + .iter() + .for_each(|target| target.to_tokens(e)); + e.space(); + self.from_clause.iter().for_each(|from| from.to_tokens(e)); + e.group_end(); + } +} diff --git a/crates/pgt_pretty_print_codegen/src/token_kind.rs b/crates/pgt_pretty_print_codegen/src/token_kind.rs index e8a5ee06..e926acbb 100644 --- a/crates/pgt_pretty_print_codegen/src/token_kind.rs +++ b/crates/pgt_pretty_print_codegen/src/token_kind.rs @@ -77,7 +77,7 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { enum_variants.push(quote! { #kind_ident }); from_kw_match_arms.push(quote! { - #kw => Some(SyntaxKind::#kind_ident) + #kw => Some(TokenKind::#kind_ident) }); } From 75101a6997413621f7030f62d204282b525e66f0 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 22 Aug 2025 09:36:54 +0200 Subject: [PATCH 05/15] progress --- Cargo.lock | 30 +++ Cargo.toml | 2 + crates/pgt_pretty_print/Cargo.toml | 3 + crates/pgt_pretty_print/src/lib.rs | 3 +- crates/pgt_pretty_print/src/nodes.rs | 122 ++++++++- crates/pgt_pretty_print/src/renderer.rs | 233 ++++++++++++++++++ ...tty_print__nodes__test__simple_select.snap | 137 ++++++++++ .../data/long_select_should_break_40.sql | 1 + .../data/long_select_should_break_80.sql | 1 + .../tests/data/minimal_120.sql | 1 + .../tests/data/minimal_80.sql | 1 + .../tests/data/nested_column_refs_80.sql | 1 + .../tests/data/select_with_alias_80.sql | 1 + .../tests/data/select_with_schema_80.sql | 1 + .../data/short_select_stays_inline_80.sql | 1 + .../tests/data/simple_select_20.sql | 1 + .../tests/data/simple_select_80.sql | 1 + .../tests__long_select_should_break_40.snap | 10 + .../tests__long_select_should_break_80.snap | 11 + .../tests/snapshots/tests__minimal_120.snap | 6 + .../tests/snapshots/tests__minimal_80.snap | 6 + .../tests__nested_column_refs_80.snap | 6 + .../tests__select_with_alias_80.snap | 6 + .../tests__select_with_schema_80.snap | 6 + .../tests__short_select_stays_inline_80.snap | 6 + .../snapshots/tests__simple_select_20.snap | 11 + .../snapshots/tests__simple_select_80.snap | 6 + crates/pgt_pretty_print/tests/tests.rs | 116 +++++++++ .../src/token_kind.rs | 53 +++- 29 files changed, 764 insertions(+), 19 deletions(-) create mode 100644 crates/pgt_pretty_print/src/renderer.rs create mode 100644 crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap create mode 100644 crates/pgt_pretty_print/tests/data/long_select_should_break_40.sql create mode 100644 crates/pgt_pretty_print/tests/data/long_select_should_break_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/minimal_120.sql create mode 100644 crates/pgt_pretty_print/tests/data/minimal_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/nested_column_refs_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/select_with_alias_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/select_with_schema_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/short_select_stays_inline_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/simple_select_20.sql create mode 100644 crates/pgt_pretty_print/tests/data/simple_select_80.sql create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_40.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__minimal_120.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__minimal_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__nested_column_refs_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__select_with_alias_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__select_with_schema_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__short_select_stays_inline_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__simple_select_20.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__simple_select_80.snap create mode 100644 crates/pgt_pretty_print/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 129e7e01..7c1b1cd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -782,6 +782,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "camino" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" + [[package]] name = "cast" version = "0.3.0" @@ -1236,6 +1242,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dir-test" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c013fe825864f3e4593f36426c1fa7a74f5603f13ca8d1af7a990c1cd94a79" +dependencies = [ + "dir-test-macros", +] + +[[package]] +name = "dir-test-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d42f54d7b4a6bc2400fe5b338e35d1a335787585375322f49c5d5fe7b243da7e" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "directories" version = "5.0.1" @@ -3027,6 +3054,9 @@ dependencies = [ name = "pgt_pretty_print" version = "0.0.0" dependencies = [ + "camino", + "dir-test", + "insta", "pgt_pretty_print_codegen", "pgt_query", ] diff --git a/Cargo.toml b/Cargo.toml index 841f3c86..1f3d4eb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,9 @@ biome_js_syntax = "0.5.7" biome_rowan = "0.5.7" biome_string_case = "0.5.8" bpaf = { version = "0.9.15", features = ["derive"] } +camino = "1.1.9" crossbeam = "0.8.4" +dir-test = "0.4.1" enumflags2 = "0.7.11" ignore = "0.4.23" indexmap = { version = "2.6.0", features = ["serde"] } diff --git a/crates/pgt_pretty_print/Cargo.toml b/crates/pgt_pretty_print/Cargo.toml index d6772e6c..9e8195dd 100644 --- a/crates/pgt_pretty_print/Cargo.toml +++ b/crates/pgt_pretty_print/Cargo.toml @@ -16,3 +16,6 @@ pgt_pretty_print_codegen.workspace = true pgt_query.workspace = true [dev-dependencies] +camino.workspace = true +dir-test.workspace = true +insta.workspace = true diff --git a/crates/pgt_pretty_print/src/lib.rs b/crates/pgt_pretty_print/src/lib.rs index 93cfac23..4ff6d335 100644 --- a/crates/pgt_pretty_print/src/lib.rs +++ b/crates/pgt_pretty_print/src/lib.rs @@ -1,5 +1,6 @@ mod codegen; -mod emitter; +pub mod emitter; mod nodes; +pub mod renderer; pub use crate::codegen::token_kind::TokenKind; diff --git a/crates/pgt_pretty_print/src/nodes.rs b/crates/pgt_pretty_print/src/nodes.rs index 374f55ed..c5bb64a2 100644 --- a/crates/pgt_pretty_print/src/nodes.rs +++ b/crates/pgt_pretty_print/src/nodes.rs @@ -1,6 +1,6 @@ use crate::{ TokenKind, - emitter::{EventEmitter, ToTokens}, + emitter::{EventEmitter, LineType, ToTokens}, }; impl ToTokens for pgt_query::Node { @@ -15,6 +15,10 @@ impl ToTokens for pgt_query::protobuf::node::Node { fn to_tokens(&self, e: &mut EventEmitter) { match self { pgt_query::protobuf::node::Node::SelectStmt(stmt) => stmt.as_ref().to_tokens(e), + pgt_query::protobuf::node::Node::ResTarget(target) => target.to_tokens(e), + pgt_query::protobuf::node::Node::ColumnRef(col_ref) => col_ref.to_tokens(e), + pgt_query::protobuf::node::Node::String(string) => string.to_tokens(e), + pgt_query::protobuf::node::Node::RangeVar(string) => string.to_tokens(e), _ => { unimplemented!("Node type {:?} not implemented for to_tokens", self); } @@ -25,13 +29,117 @@ impl ToTokens for pgt_query::protobuf::node::Node { impl ToTokens for pgt_query::protobuf::SelectStmt { fn to_tokens(&self, e: &mut EventEmitter) { e.group_start(None, false); + e.token(TokenKind::SELECT_KW); - e.space(); - self.target_list - .iter() - .for_each(|target| target.to_tokens(e)); - e.space(); - self.from_clause.iter().for_each(|from| from.to_tokens(e)); + + if !self.target_list.is_empty() { + e.indent_start(); + e.line(LineType::SoftOrSpace); + + for (i, target) in self.target_list.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + target.to_tokens(e); + } + e.indent_end(); + } + + if !self.from_clause.is_empty() { + e.line(LineType::SoftOrSpace); + e.token(TokenKind::FROM_KW); + e.line(LineType::SoftOrSpace); + + e.indent_start(); + for (i, from) in self.from_clause.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + from.to_tokens(e); + } + e.indent_end(); + } + + e.token(TokenKind::SEMICOLON); + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::ResTarget { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(None, false); + + if let Some(ref val) = self.val { + val.to_tokens(e); + } + + if !self.name.is_empty() { + e.space(); + e.token(TokenKind::AS_KW); + e.space(); + e.token(TokenKind::IDENT(self.name.clone())); + } + e.group_end(); } } + +impl ToTokens for pgt_query::protobuf::ColumnRef { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(None, false); + + for (i, field) in self.fields.iter().enumerate() { + if i > 0 { + e.token(TokenKind::DOT); + } + field.to_tokens(e); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::String { + fn to_tokens(&self, e: &mut EventEmitter) { + e.token(TokenKind::IDENT(self.sval.clone())); + } +} + +impl ToTokens for pgt_query::protobuf::RangeVar { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(None, false); + + if !self.schemaname.is_empty() { + e.token(TokenKind::IDENT(self.schemaname.clone())); + e.token(TokenKind::DOT); + } + + e.token(TokenKind::IDENT(self.relname.clone())); + + e.group_end(); + } +} + +#[cfg(test)] +mod test { + use crate::emitter::{EventEmitter, ToTokens}; + + use insta::assert_debug_snapshot; + + #[test] + fn simple_select() { + let input = "select public.t.a as y, b as z, c from t where id = @id;"; + + let parsed = pgt_query::parse(input).expect("Failed to parse SQL"); + + let ast = parsed.root().expect("No root node found"); + + let mut emitter = EventEmitter::new(); + ast.to_tokens(&mut emitter); + + assert_debug_snapshot!(emitter.events); + } +} diff --git a/crates/pgt_pretty_print/src/renderer.rs b/crates/pgt_pretty_print/src/renderer.rs new file mode 100644 index 00000000..6606e94e --- /dev/null +++ b/crates/pgt_pretty_print/src/renderer.rs @@ -0,0 +1,233 @@ +use crate::emitter::{LayoutEvent, LineType}; +use std::fmt::Write; + +#[derive(Debug, Clone)] +pub enum IndentStyle { + Spaces, + Tabs, +} + +#[derive(Debug, Clone)] +pub struct RenderConfig { + pub max_line_length: usize, + pub indent_size: usize, + pub indent_style: IndentStyle, +} + +impl Default for RenderConfig { + fn default() -> Self { + Self { + max_line_length: 80, + indent_size: 2, + indent_style: IndentStyle::Spaces, + } + } +} + +pub struct Renderer { + config: RenderConfig, + writer: W, + current_line_length: usize, + indent_level: usize, + at_line_start: bool, +} + +impl Renderer { + pub fn new(writer: W, config: RenderConfig) -> Self { + Self { + config, + writer, + current_line_length: 0, + indent_level: 0, + at_line_start: true, + } + } + + pub fn render(&mut self, events: Vec) -> Result<(), std::fmt::Error> { + self.render_events(&events) + } + + fn render_events(&mut self, events: &[LayoutEvent]) -> Result<(), std::fmt::Error> { + let mut i = 0; + while i < events.len() { + match &events[i] { + LayoutEvent::Token(token) => { + let token_text = token.render(); + self.write_text(&token_text)?; + i += 1; + } + LayoutEvent::Space => { + self.write_space()?; + i += 1; + } + LayoutEvent::Line(line_type) => { + self.handle_line(&line_type)?; + i += 1; + } + LayoutEvent::GroupStart { .. } => { + let group_end = self.find_group_end(events, i); + let group_slice = &events[i..=group_end]; + self.render_group(group_slice)?; + i = group_end + 1; + } + LayoutEvent::GroupEnd => { + return Err(std::fmt::Error); // Should not happen + } + LayoutEvent::IndentStart => { + self.indent_level += 1; + i += 1; + } + LayoutEvent::IndentEnd => { + self.indent_level = self.indent_level.saturating_sub(1); + i += 1; + } + } + } + Ok(()) + } + + fn render_group(&mut self, group_events: &[LayoutEvent]) -> Result<(), std::fmt::Error> { + if let Some(single_line) = self.try_single_line(group_events) { + let would_fit = + self.current_line_length + single_line.len() <= self.config.max_line_length; + if would_fit { + self.write_text(&single_line)?; + return Ok(()); + } + } + + // break version - render each event directly without recursion + for event in group_events { + match event { + LayoutEvent::Token(token) => { + let text = token.render(); + self.write_text(&text)?; + } + LayoutEvent::Space => { + self.write_space()?; + } + LayoutEvent::Line(_) => { + self.write_line_break()?; + } + LayoutEvent::GroupStart { .. } | LayoutEvent::GroupEnd => { + // skip group boundaries + } + LayoutEvent::IndentStart => { + self.indent_level += 1; + } + LayoutEvent::IndentEnd => { + self.indent_level = self.indent_level.saturating_sub(1); + } + } + } + Ok(()) + } + + fn try_single_line(&self, group_events: &[LayoutEvent]) -> Option { + let mut buffer = String::new(); + let mut has_hard_breaks = false; + + for event in group_events { + match event { + LayoutEvent::Token(token) => { + let text = token.render(); + buffer.push_str(&text); + } + LayoutEvent::Space => { + buffer.push(' '); + } + LayoutEvent::Line(LineType::Hard) => { + has_hard_breaks = true; + break; + } + LayoutEvent::Line(LineType::Soft) => { + // soft lines disappear in single-line mode (no space) + } + LayoutEvent::Line(LineType::SoftOrSpace) => { + buffer.push(' '); // Becomes space in single-line mode + } + LayoutEvent::GroupStart { .. } | LayoutEvent::GroupEnd => { + // skip group markers for single line test + } + LayoutEvent::IndentStart | LayoutEvent::IndentEnd => { + // skip indent changes for single line test + } + } + } + + if has_hard_breaks { None } else { Some(buffer) } + } + + fn handle_line(&mut self, line_type: &LineType) -> Result<(), std::fmt::Error> { + match line_type { + LineType::Hard => { + self.write_line_break()?; + } + LineType::Soft | LineType::SoftOrSpace => { + // For now, just treat as space outside groups + self.write_space()?; + } + } + Ok(()) + } + + fn find_group_end(&self, events: &[LayoutEvent], start: usize) -> usize { + let mut depth = 0; + for i in start..events.len() { + match &events[i] { + LayoutEvent::GroupStart { .. } => depth += 1, + LayoutEvent::GroupEnd => { + depth -= 1; + if depth == 0 { + return i; + } + } + _ => {} + } + } + events.len() - 1 // Fallback + } + + fn write_text(&mut self, text: &str) -> Result<(), std::fmt::Error> { + if self.at_line_start { + self.write_indentation()?; + self.at_line_start = false; + } + + write!(self.writer, "{}", text)?; + self.current_line_length += text.len(); + Ok(()) + } + + fn write_space(&mut self) -> Result<(), std::fmt::Error> { + if !self.at_line_start { + write!(self.writer, " ")?; + self.current_line_length += 1; + } + Ok(()) + } + + fn write_line_break(&mut self) -> Result<(), std::fmt::Error> { + writeln!(self.writer)?; + self.current_line_length = 0; + self.at_line_start = true; + Ok(()) + } + + fn write_indentation(&mut self) -> Result<(), std::fmt::Error> { + let indent_str = match self.config.indent_style { + IndentStyle::Spaces => { + let spaces = " ".repeat(self.indent_level * self.config.indent_size); + self.current_line_length += spaces.len(); + spaces + } + IndentStyle::Tabs => { + let tabs = "\t".repeat(self.indent_level); + self.current_line_length += self.indent_level * self.config.indent_size; // Approximate + tabs + } + }; + write!(self.writer, "{}", indent_str)?; + Ok(()) + } +} diff --git a/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap b/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap new file mode 100644 index 00000000..6e71658a --- /dev/null +++ b/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap @@ -0,0 +1,137 @@ +--- +source: crates/pgt_pretty_print/src/nodes.rs +expression: emitter.events +snapshot_kind: text +--- +[ + GroupStart { + id: None, + break_parent: false, + }, + Token( + SELECT_KW, + ), + IndentStart, + Line( + SoftOrSpace, + ), + GroupStart { + id: None, + break_parent: false, + }, + GroupStart { + id: None, + break_parent: false, + }, + Token( + IDENT( + "public", + ), + ), + Token( + DOT, + ), + Token( + IDENT( + "t", + ), + ), + Token( + DOT, + ), + Token( + IDENT( + "a", + ), + ), + GroupEnd, + Space, + Token( + AS_KW, + ), + Space, + Token( + IDENT( + "y", + ), + ), + GroupEnd, + Token( + COMMA, + ), + Line( + SoftOrSpace, + ), + GroupStart { + id: None, + break_parent: false, + }, + GroupStart { + id: None, + break_parent: false, + }, + Token( + IDENT( + "b", + ), + ), + GroupEnd, + Space, + Token( + AS_KW, + ), + Space, + Token( + IDENT( + "z", + ), + ), + GroupEnd, + Token( + COMMA, + ), + Line( + SoftOrSpace, + ), + GroupStart { + id: None, + break_parent: false, + }, + GroupStart { + id: None, + break_parent: false, + }, + Token( + IDENT( + "c", + ), + ), + GroupEnd, + GroupEnd, + IndentEnd, + Line( + SoftOrSpace, + ), + Token( + FROM_KW, + ), + Line( + SoftOrSpace, + ), + IndentStart, + GroupStart { + id: None, + break_parent: false, + }, + Token( + IDENT( + "t", + ), + ), + GroupEnd, + IndentEnd, + Token( + SEMICOLON, + ), + GroupEnd, +] diff --git a/crates/pgt_pretty_print/tests/data/long_select_should_break_40.sql b/crates/pgt_pretty_print/tests/data/long_select_should_break_40.sql new file mode 100644 index 00000000..422af566 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/long_select_should_break_40.sql @@ -0,0 +1 @@ +SELECT very_long_column_name_one, very_long_column_name_two FROM long_table_name \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/long_select_should_break_80.sql b/crates/pgt_pretty_print/tests/data/long_select_should_break_80.sql new file mode 100644 index 00000000..3fe39f03 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/long_select_should_break_80.sql @@ -0,0 +1 @@ +SELECT very_long_column_name_one, very_long_column_name_two, very_long_column_name_three FROM long_table_name \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/minimal_120.sql b/crates/pgt_pretty_print/tests/data/minimal_120.sql new file mode 100644 index 00000000..a69bea20 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/minimal_120.sql @@ -0,0 +1 @@ +SELECT a FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/minimal_80.sql b/crates/pgt_pretty_print/tests/data/minimal_80.sql new file mode 100644 index 00000000..a69bea20 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/minimal_80.sql @@ -0,0 +1 @@ +SELECT a FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/nested_column_refs_80.sql b/crates/pgt_pretty_print/tests/data/nested_column_refs_80.sql new file mode 100644 index 00000000..aa3fd551 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/nested_column_refs_80.sql @@ -0,0 +1 @@ +SELECT schema.table.column FROM schema.table \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/select_with_alias_80.sql b/crates/pgt_pretty_print/tests/data/select_with_alias_80.sql new file mode 100644 index 00000000..6428e715 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/select_with_alias_80.sql @@ -0,0 +1 @@ +SELECT a AS x, b AS y, c FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/select_with_schema_80.sql b/crates/pgt_pretty_print/tests/data/select_with_schema_80.sql new file mode 100644 index 00000000..08d5f59e --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/select_with_schema_80.sql @@ -0,0 +1 @@ +SELECT public.t.a, t.b, c FROM public.t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/short_select_stays_inline_80.sql b/crates/pgt_pretty_print/tests/data/short_select_stays_inline_80.sql new file mode 100644 index 00000000..a69bea20 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/short_select_stays_inline_80.sql @@ -0,0 +1 @@ +SELECT a FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/simple_select_20.sql b/crates/pgt_pretty_print/tests/data/simple_select_20.sql new file mode 100644 index 00000000..9c497a60 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/simple_select_20.sql @@ -0,0 +1 @@ +SELECT a, b, c FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/simple_select_80.sql b/crates/pgt_pretty_print/tests/data/simple_select_80.sql new file mode 100644 index 00000000..9c497a60 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/simple_select_80.sql @@ -0,0 +1 @@ +SELECT a, b, c FROM t \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_40.snap b/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_40.snap new file mode 100644 index 00000000..0178e1ca --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_40.snap @@ -0,0 +1,10 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/long_select_should_break_40.sql +snapshot_kind: text +--- +SELECT + very_long_column_name_one, + very_long_column_name_two +FROM + long_table_name; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_80.snap new file mode 100644 index 00000000..0f51564e --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__long_select_should_break_80.snap @@ -0,0 +1,11 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/long_select_should_break_80.sql +snapshot_kind: text +--- +SELECT + very_long_column_name_one, + very_long_column_name_two, + very_long_column_name_three +FROM + long_table_name; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__minimal_120.snap b/crates/pgt_pretty_print/tests/snapshots/tests__minimal_120.snap new file mode 100644 index 00000000..9e1b86dd --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__minimal_120.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/minimal_120.sql +snapshot_kind: text +--- +SELECT a FROM t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__minimal_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__minimal_80.snap new file mode 100644 index 00000000..b14b3f0a --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__minimal_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/minimal_80.sql +snapshot_kind: text +--- +SELECT a FROM t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__nested_column_refs_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__nested_column_refs_80.snap new file mode 100644 index 00000000..f631f99f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__nested_column_refs_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/nested_column_refs_80.sql +snapshot_kind: text +--- +SELECT schema.table.column FROM schema.table; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__select_with_alias_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__select_with_alias_80.snap new file mode 100644 index 00000000..33347408 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__select_with_alias_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/select_with_alias_80.sql +snapshot_kind: text +--- +SELECT a AS x, b AS y, c FROM t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__select_with_schema_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__select_with_schema_80.snap new file mode 100644 index 00000000..2c2c11e4 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__select_with_schema_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/select_with_schema_80.sql +snapshot_kind: text +--- +SELECT public.t.a, t.b, c FROM public.t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__short_select_stays_inline_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__short_select_stays_inline_80.snap new file mode 100644 index 00000000..7ec2633e --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__short_select_stays_inline_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/short_select_stays_inline_80.sql +snapshot_kind: text +--- +SELECT a FROM t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_20.snap b/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_20.snap new file mode 100644 index 00000000..30ef22ca --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_20.snap @@ -0,0 +1,11 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/simple_select_20.sql +snapshot_kind: text +--- +SELECT + a, + b, + c +FROM + t; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_80.snap new file mode 100644 index 00000000..062c912f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__simple_select_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/simple_select_80.sql +snapshot_kind: text +--- +SELECT a, b, c FROM t; diff --git a/crates/pgt_pretty_print/tests/tests.rs b/crates/pgt_pretty_print/tests/tests.rs new file mode 100644 index 00000000..983ce9f2 --- /dev/null +++ b/crates/pgt_pretty_print/tests/tests.rs @@ -0,0 +1,116 @@ +use camino::Utf8Path; +use dir_test::{Fixture, dir_test}; +use insta::{assert_snapshot, with_settings}; + +use pgt_pretty_print::{ + emitter::{EventEmitter, ToTokens}, + renderer::{IndentStyle, RenderConfig, Renderer}, +}; + +#[dir_test( + dir: "$CARGO_MANIFEST_DIR/tests/data/", + glob: "*.sql", +)] +fn test_formatter(fixture: Fixture<&str>) { + let content = fixture.content(); + let absolute_fixture_path = Utf8Path::new(fixture.path()); + let input_file = absolute_fixture_path; + let test_name = absolute_fixture_path + .file_name() + .and_then(|x| x.strip_suffix(".sql")) + .unwrap(); + + // extract line length from filename (e.g., "simple_select_80" -> 80) + let max_line_length = test_name + .split('_') + .next_back() + .and_then(|s| s.parse::().ok()) + .unwrap_or(80); + + let parsed = pgt_query::parse(content).expect("Failed to parse SQL"); + let mut ast = parsed.into_root().expect("No root node found"); + + let mut emitter = EventEmitter::new(); + ast.to_tokens(&mut emitter); + + let mut output = String::new(); + let config = RenderConfig { + max_line_length, + indent_size: 2, + indent_style: IndentStyle::Spaces, + }; + let mut renderer = Renderer::new(&mut output, config); + renderer.render(emitter.events).expect("Failed to render"); + + let parsed_output = pgt_query::parse(&output).expect("Failed to parse SQL"); + let mut parsed_ast = parsed_output.into_root().expect("No root node found"); + + // the location fields are now different in the two ASTs + clear_location(&mut parsed_ast); + clear_location(&mut ast); + + assert_eq!(ast, parsed_ast); + + with_settings!({ + omit_expression => true, + input_file => input_file, + }, { + assert_snapshot!(test_name, output); + }); +} + +fn clear_location(node: &mut pgt_query::NodeEnum) { + unsafe { + node.iter_mut().for_each(|n| match n { + pgt_query::NodeMut::ColumnRef(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::ParamRef(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::AExpr(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::TypeCast(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::CollateClause(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::FuncCall(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::AArrayExpr(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::ResTarget(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::SortBy(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::WindowDef(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::TypeName(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::ColumnDef(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::DefElem(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::XmlSerialize(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::AConst(n) => { + (*n).location = 0; + } + pgt_query::NodeMut::RangeVar(n) => { + (*n).location = 0; + } + _ => {} + }); + } +} diff --git a/crates/pgt_pretty_print_codegen/src/token_kind.rs b/crates/pgt_pretty_print_codegen/src/token_kind.rs index e926acbb..85eab61a 100644 --- a/crates/pgt_pretty_print_codegen/src/token_kind.rs +++ b/crates/pgt_pretty_print_codegen/src/token_kind.rs @@ -34,16 +34,16 @@ const LITERALS: &[&str] = &[ ]; const VARIANT_DATA: &[(&str, &str)] = &[ - ("String", "String"), - ("EscString", "String"), // E'hello\nworld' - ("DollarQuotedString", "String"), // $$hello world$$ - ("IntNumber", "i64"), // 123, -456 - ("FloatNumber", "f64"), // 123.45, 1.2e-3 - ("BitString", "String"), // B'1010', X'FF' - ("ByteString", "String"), // Similar to bit string - ("Ident", "String"), // user_id, table_name - ("PositionalParam", "u32"), // $1, $2, $3 (the number matters!) - ("Comment", "String"), // /* comment text */ + ("STRING", "String"), + ("ESC_STRING", "String"), // E'hello\nworld' + ("DOLLAR_QUOTED_STRING", "String"), // $$hello world$$ + ("INT_NUMBER", "i64"), // 123, -456 + ("FLOAT_NUMBER", "f64"), // 123.45, 1.2e-3 + ("BIT_STRING", "String"), // B'1010', X'FF' + ("BYTE_STRING", "String"), // Similar to bit string + ("IDENT", "String"), // user_id, table_name + ("POSITIONAL_PARAM", "u32"), // $1, $2, $3 (the number matters!) + ("COMMENT", "String"), // /* comment text */ ]; pub fn token_kind_mod() -> proc_macro2::TokenStream { @@ -53,6 +53,7 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { let mut enum_variants: Vec = Vec::new(); let mut from_kw_match_arms: Vec = Vec::new(); + let mut render_kw_match_arms: Vec = Vec::new(); // helper function to create a variant quote for enum // used to handle variants with data types @@ -79,6 +80,9 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { from_kw_match_arms.push(quote! { #kw => Some(TokenKind::#kind_ident) }); + render_kw_match_arms.push(quote! { + TokenKind::#kind_ident => #kw.to_uppercase() + }); } // collect extra keywords @@ -102,7 +106,7 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { }); quote! { - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + #[derive(Clone, PartialEq, Debug)] pub enum TokenKind { #(#enum_variants),*, } @@ -116,5 +120,32 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { } } } + + impl TokenKind { + pub fn render(&self) -> String { + match self { + TokenKind::SEMICOLON => ";".to_string(), + TokenKind::COMMA => ",".to_string(), + TokenKind::L_PAREN => "(".to_string(), + TokenKind::R_PAREN => ")".to_string(), + TokenKind::L_BRACK => "[".to_string(), + TokenKind::R_BRACK => "]".to_string(), + TokenKind::DOT => ".".to_string(), + TokenKind::DOUBLE_COLON => "::".to_string(), + TokenKind::DOLLAR => "$".to_string(), + TokenKind::IDENT(ident) => ident.clone(), + TokenKind::STRING(s) => s.clone(), + TokenKind::ESC_STRING(s) => s.clone(), + TokenKind::DOLLAR_QUOTED_STRING(s) => s.clone(), + TokenKind::INT_NUMBER(n) => n.to_string(), + TokenKind::FLOAT_NUMBER(n) => n.to_string(), + TokenKind::BIT_STRING(s) => s.clone(), + TokenKind::BYTE_STRING(s) => s.clone(), + TokenKind::NULL => "NULL".to_string(), + #(#render_kw_match_arms),*, + _ => format!("{:?}", self), // Fallback for other variants + } + } + } } } From 111de33e6144a1f3b17fccc624fe0b91228e1db5 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 22 Aug 2025 10:05:13 +0200 Subject: [PATCH 06/15] progress --- crates/pgt_pretty_print/INTEGRATION_PLAN.md | 165 ++++++++++++++++++ crates/pgt_pretty_print/src/nodes.rs | 36 ++++ crates/pgt_pretty_print/src/renderer.rs | 55 ++++-- .../tests/data/break_parent_test_80.sql | 1 + .../tests__break_parent_test_80.snap | 12 ++ 5 files changed, 258 insertions(+), 11 deletions(-) create mode 100644 crates/pgt_pretty_print/INTEGRATION_PLAN.md create mode 100644 crates/pgt_pretty_print/tests/data/break_parent_test_80.sql create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__break_parent_test_80.snap diff --git a/crates/pgt_pretty_print/INTEGRATION_PLAN.md b/crates/pgt_pretty_print/INTEGRATION_PLAN.md new file mode 100644 index 00000000..184603a7 --- /dev/null +++ b/crates/pgt_pretty_print/INTEGRATION_PLAN.md @@ -0,0 +1,165 @@ +# PostgreSQL Pretty Printer Integration Plan + +## Current Status + +The pretty printer foundation is **complete and working**! Basic SQL formatting is functional with: +- ✅ SELECT statements with aliases, schema qualification +- ✅ Line length-based breaking (configurable via filename suffix) +- ✅ Proper comma placement and indentation +- ✅ Comprehensive test suite with snapshot testing +- ✅ AST integrity verification (location-aware comparison) + +## Architecture Overview + +``` +SQL Input → pgt_query::parse() → AST → ToTokens → Layout Events → Renderer → Formatted SQL +``` + +**Key Components:** +- **ToTokens trait**: Converts AST nodes to layout events +- **Layout Events**: `Token`, `Space`, `Line(Hard/Soft/SoftOrSpace)`, `GroupStart/End`, `IndentStart/End` +- **Renderer**: Two-phase prettier-style algorithm (try single line, else break) + +## Renderer Implementation Status + +### ✅ Completed +- **Core rendering pipeline**: Event processing, text/space/line output +- **Basic grouping**: Single-line vs multi-line decisions +- **Indentation**: Configurable spaces/tabs with proper nesting +- **Line length enforcement**: Respects `max_line_length` config +- **Token rendering**: Keywords, identifiers, punctuation +- **Break propagation**: Child groups with `break_parent: true` force parent groups to break +- **Nested group independence**: Inner groups make independent fit decisions when outer groups break +- **Stack overflow elimination**: Fixed infinite recursion in renderer + +### ❌ Missing Features (Priority Order) + +#### 1. **Group ID References** (Medium Priority) +**Issue**: Groups can't reference each other's break decisions. + +```rust +// Missing: Conditional formatting based on other groups +GroupStart { id: Some("params") } +// ... later reference "params" group's break decision +``` + +**Implementation**: +- Track group break decisions by ID +- Add conditional breaking logic + +#### 2. **Advanced Line Types** (Medium Priority) +**Issue**: `LineType::Soft` vs `LineType::SoftOrSpace` handling could be more sophisticated. + +**Current behavior**: +- `Hard`: Always breaks +- `Soft`: Breaks if group breaks, disappears if inline +- `SoftOrSpace`: Breaks if group breaks, becomes space if inline + +**Enhancement**: Better handling of soft line semantics in complex nesting. + +#### 3. **Performance Optimizations** (Low Priority) +- **Early bailout**: Stop single-line calculation when length exceeds limit +- **Caching**: Memoize group fit calculations for repeated structures +- **String building**: More efficient string concatenation + +## AST Node Coverage Status + +### ✅ Implemented ToTokens +- `SelectStmt`: Basic SELECT with FROM clause +- `ResTarget`: Column targets with aliases +- `ColumnRef`: Column references (schema.table.column) +- `String`: String literals in column references +- `RangeVar`: Table references with schema +- `FuncCall`: Function calls with break propagation support + +### ❌ Missing ToTokens (Add as needed) +- `InsertStmt`, `UpdateStmt`, `DeleteStmt`: DML statements +- `WhereClause`, `JoinExpr`: WHERE conditions and JOINs +- `AExpr`: Binary/unary expressions (`a = b`, `a + b`) +- `AConst`: Literals (numbers, strings, booleans) +- `SubLink`: Subqueries +- `CaseExpr`: CASE expressions +- `WindowFunc`: Window functions +- `AggRef`: Aggregate functions +- `TypeCast`: Type casting (`::int`) + +## Testing Infrastructure + +### ✅ Current +- **dir-test integration**: Drop SQL files → automatic snapshot testing +- **Line length extraction**: `filename_80.sql` → `max_line_length: 80` +- **AST integrity verification**: Ensures no data loss during formatting +- **Location field handling**: Clears location differences for comparison + +### 🔄 Enhancements Needed +- **Add more test cases**: Complex queries, edge cases +- **Performance benchmarks**: Large SQL file formatting speed +- **Configuration testing**: Different indent styles, line lengths +- **Break propagation testing**: Verified with `FuncCall` implementation + +## Integration Steps + +### ✅ Phase 1: Core Renderer Fixes (COMPLETED) +1. ✅ **Fix break propagation**: Implemented proper `break_parent` handling +2. ✅ **Fix nested groups**: Allow independent fit decisions +3. ✅ **Fix stack overflow**: Eliminated infinite recursion in renderer +4. ✅ **Test with complex cases**: Added `FuncCall` with break propagation test + +### Phase 2: AST Coverage Expansion (2-4 days) +1. **Add WHERE clause support**: `WhereClause`, `AExpr` ToTokens +2. **Add basic expressions**: `AConst`, binary operators +3. **Add INSERT/UPDATE/DELETE**: Basic DML statements + +### Phase 3: Advanced Features (1-2 days) +1. **Implement group ID system**: Cross-group references +2. **Add performance optimizations**: Early bailout, caching +3. **Enhanced line breaking**: Better soft line semantics + +### Phase 4: Production Ready (1-2 days) +1. **Comprehensive testing**: Large SQL files, edge cases +2. **Performance validation**: Benchmark against alternatives +3. **Documentation**: API docs, integration examples + +## API Integration Points + +```rust +// Main formatting function +pub fn format_sql(sql: &str, config: RenderConfig) -> Result { + let parsed = pgt_query::parse(sql)?; + let ast = parsed.root()?; + + let mut emitter = EventEmitter::new(); + ast.to_tokens(&mut emitter); + + let mut output = String::new(); + let mut renderer = Renderer::new(&mut output, config); + renderer.render(emitter.events)?; + + Ok(output) +} + +// Configuration +pub struct RenderConfig { + pub max_line_length: usize, // 80, 100, 120, etc. + pub indent_size: usize, // 2, 4, etc. + pub indent_style: IndentStyle, // Spaces, Tabs +} +``` + +## Estimated Completion Timeline + +- ✅ **Phase 1** (Core fixes): COMPLETED → **Fully functional renderer** +- **Phase 2** (AST coverage): 4 days → **Supports most common SQL** +- **Phase 3** (Advanced): 2 days → **Production-grade formatting** +- **Phase 4** (Polish): 2 days → **Integration ready** + +**Total: ~1 week remaining** for complete production-ready PostgreSQL pretty printer. + +## Current Limitations + +1. **Limited SQL coverage**: Only basic SELECT statements and function calls +2. **No error recovery**: Unimplemented AST nodes cause panics +3. **No configuration validation**: Invalid configs not checked +4. **Missing group ID system**: Cross-group conditional formatting not yet implemented + +The core renderer foundation is now solid with proper break propagation and nested group handling - the remaining work is primarily expanding AST node coverage. \ No newline at end of file diff --git a/crates/pgt_pretty_print/src/nodes.rs b/crates/pgt_pretty_print/src/nodes.rs index c5bb64a2..b7f3c042 100644 --- a/crates/pgt_pretty_print/src/nodes.rs +++ b/crates/pgt_pretty_print/src/nodes.rs @@ -19,6 +19,7 @@ impl ToTokens for pgt_query::protobuf::node::Node { pgt_query::protobuf::node::Node::ColumnRef(col_ref) => col_ref.to_tokens(e), pgt_query::protobuf::node::Node::String(string) => string.to_tokens(e), pgt_query::protobuf::node::Node::RangeVar(string) => string.to_tokens(e), + pgt_query::protobuf::node::Node::FuncCall(func_call) => func_call.to_tokens(e), _ => { unimplemented!("Node type {:?} not implemented for to_tokens", self); } @@ -123,6 +124,41 @@ impl ToTokens for pgt_query::protobuf::RangeVar { } } +impl ToTokens for pgt_query::protobuf::FuncCall { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(None, false); + + // Render function name + if let Some(first_name) = self.funcname.first() { + first_name.to_tokens(e); + } + + e.token(TokenKind::L_PAREN); + + // For function arguments, use break_parent: true to test break propagation + if !self.args.is_empty() { + e.group_start(None, true); // break_parent: true + e.line(LineType::SoftOrSpace); + e.indent_start(); + + for (i, arg) in self.args.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + arg.to_tokens(e); + } + + e.indent_end(); + e.line(LineType::SoftOrSpace); + e.group_end(); + } + + e.token(TokenKind::R_PAREN); + e.group_end(); + } +} + #[cfg(test)] mod test { use crate::emitter::{EventEmitter, ToTokens}; diff --git a/crates/pgt_pretty_print/src/renderer.rs b/crates/pgt_pretty_print/src/renderer.rs index 6606e94e..a9f26cac 100644 --- a/crates/pgt_pretty_print/src/renderer.rs +++ b/crates/pgt_pretty_print/src/renderer.rs @@ -87,36 +87,69 @@ impl Renderer { } fn render_group(&mut self, group_events: &[LayoutEvent]) -> Result<(), std::fmt::Error> { - if let Some(single_line) = self.try_single_line(group_events) { - let would_fit = - self.current_line_length + single_line.len() <= self.config.max_line_length; - if would_fit { - self.write_text(&single_line)?; - return Ok(()); + // Check for break_parent in any nested group + let should_break = self.has_break_parent(group_events); + + if !should_break { + if let Some(single_line) = self.try_single_line(group_events) { + let would_fit = + self.current_line_length + single_line.len() <= self.config.max_line_length; + if would_fit { + self.write_text(&single_line)?; + return Ok(()); + } } } - // break version - render each event directly without recursion - for event in group_events { - match event { + // break version - process events with proper nested group handling + self.render_events_with_breaks(group_events) + } + + fn has_break_parent(&self, events: &[LayoutEvent]) -> bool { + for event in events { + if let LayoutEvent::GroupStart { + break_parent: true, .. + } = event + { + return true; + } + } + false + } + + fn render_events_with_breaks(&mut self, events: &[LayoutEvent]) -> Result<(), std::fmt::Error> { + let mut i = 0; + while i < events.len() { + match &events[i] { LayoutEvent::Token(token) => { let text = token.render(); self.write_text(&text)?; + i += 1; } LayoutEvent::Space => { self.write_space()?; + i += 1; } LayoutEvent::Line(_) => { self.write_line_break()?; + i += 1; } - LayoutEvent::GroupStart { .. } | LayoutEvent::GroupEnd => { - // skip group boundaries + LayoutEvent::GroupStart { .. } => { + let group_end = self.find_group_end(events, i); + let inner_events = &events[i + 1..group_end]; // Skip GroupStart/GroupEnd + self.render_events_with_breaks(inner_events)?; + i = group_end + 1; + } + LayoutEvent::GroupEnd => { + i += 1; // Skip isolated group end } LayoutEvent::IndentStart => { self.indent_level += 1; + i += 1; } LayoutEvent::IndentEnd => { self.indent_level = self.indent_level.saturating_sub(1); + i += 1; } } } diff --git a/crates/pgt_pretty_print/tests/data/break_parent_test_80.sql b/crates/pgt_pretty_print/tests/data/break_parent_test_80.sql new file mode 100644 index 00000000..58fb488a --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/break_parent_test_80.sql @@ -0,0 +1 @@ +SELECT very_long_function_name(short_arg, other_arg) FROM test_table \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__break_parent_test_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__break_parent_test_80.snap new file mode 100644 index 00000000..c0acc131 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__break_parent_test_80.snap @@ -0,0 +1,12 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/break_parent_test_80.sql +snapshot_kind: text +--- +SELECT + very_long_function_name( + short_arg, + other_arg + ) +FROM + test_table; From a34467db38c745b72aabaecaf637060726eed732 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 22 Aug 2025 10:32:19 +0200 Subject: [PATCH 07/15] progress --- crates/pgt_pretty_print/src/renderer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/pgt_pretty_print/src/renderer.rs b/crates/pgt_pretty_print/src/renderer.rs index a9f26cac..f6e18aaf 100644 --- a/crates/pgt_pretty_print/src/renderer.rs +++ b/crates/pgt_pretty_print/src/renderer.rs @@ -87,7 +87,6 @@ impl Renderer { } fn render_group(&mut self, group_events: &[LayoutEvent]) -> Result<(), std::fmt::Error> { - // Check for break_parent in any nested group let should_break = self.has_break_parent(group_events); if !should_break { @@ -101,7 +100,6 @@ impl Renderer { } } - // break version - process events with proper nested group handling self.render_events_with_breaks(group_events) } @@ -136,12 +134,12 @@ impl Renderer { } LayoutEvent::GroupStart { .. } => { let group_end = self.find_group_end(events, i); - let inner_events = &events[i + 1..group_end]; // Skip GroupStart/GroupEnd + let inner_events = &events[i + 1..group_end]; // skip GroupStart/GroupEnd self.render_events_with_breaks(inner_events)?; i = group_end + 1; } LayoutEvent::GroupEnd => { - i += 1; // Skip isolated group end + i += 1; // skip isolated group end } LayoutEvent::IndentStart => { self.indent_level += 1; From 4405de66bbf06466f1e504f170d2b4e46a531ebb Mon Sep 17 00:00:00 2001 From: psteinroe Date: Mon, 25 Aug 2025 09:37:01 +0200 Subject: [PATCH 08/15] progress --- crates/pgt_pretty_print/src/nodes.rs | 12 +++++++----- crates/pgt_pretty_print/src/renderer.rs | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/pgt_pretty_print/src/nodes.rs b/crates/pgt_pretty_print/src/nodes.rs index b7f3c042..f837ec71 100644 --- a/crates/pgt_pretty_print/src/nodes.rs +++ b/crates/pgt_pretty_print/src/nodes.rs @@ -128,16 +128,18 @@ impl ToTokens for pgt_query::protobuf::FuncCall { fn to_tokens(&self, e: &mut EventEmitter) { e.group_start(None, false); - // Render function name - if let Some(first_name) = self.funcname.first() { - first_name.to_tokens(e); + for (i, name) in self.funcname.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + name.to_tokens(e); } e.token(TokenKind::L_PAREN); - // For function arguments, use break_parent: true to test break propagation if !self.args.is_empty() { - e.group_start(None, true); // break_parent: true + e.group_start(None, true); e.line(LineType::SoftOrSpace); e.indent_start(); diff --git a/crates/pgt_pretty_print/src/renderer.rs b/crates/pgt_pretty_print/src/renderer.rs index f6e18aaf..03b460a4 100644 --- a/crates/pgt_pretty_print/src/renderer.rs +++ b/crates/pgt_pretty_print/src/renderer.rs @@ -71,7 +71,7 @@ impl Renderer { i = group_end + 1; } LayoutEvent::GroupEnd => { - return Err(std::fmt::Error); // Should not happen + assert!(false, "Unmatched group end"); } LayoutEvent::IndentStart => { self.indent_level += 1; @@ -139,7 +139,7 @@ impl Renderer { i = group_end + 1; } LayoutEvent::GroupEnd => { - i += 1; // skip isolated group end + assert!(false, "Unmatched group end"); } LayoutEvent::IndentStart => { self.indent_level += 1; @@ -216,7 +216,7 @@ impl Renderer { _ => {} } } - events.len() - 1 // Fallback + assert!(false, "Unmatched group start"); } fn write_text(&mut self, text: &str) -> Result<(), std::fmt::Error> { From e91137b3caf02ad05c7ccf9906208cf103d8d9cb Mon Sep 17 00:00:00 2001 From: psteinroe Date: Tue, 26 Aug 2025 10:10:46 +0200 Subject: [PATCH 09/15] chore: add agentic loop poc --- .cargo/config.toml | 2 + Cargo.lock | 13 + Cargo.toml | 2 +- crates/pgt_pretty_print/src/renderer.rs | 2 +- crates/pgt_pretty_print/tests/tests.rs | 21 + justfile | 4 + xtask/agentic/Cargo.toml | 17 + xtask/agentic/ast_nodes.txt | 271 ++++++++ xtask/agentic/src/claude_session.rs | 112 ++++ xtask/agentic/src/lib.rs | 19 + xtask/agentic/src/main.rs | 8 + xtask/agentic/src/pretty_print.rs | 842 ++++++++++++++++++++++++ xtask/src/agentic.rs | 14 + xtask/src/flags.rs | 20 + xtask/src/main.rs | 3 +- 15 files changed, 1347 insertions(+), 3 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 xtask/agentic/Cargo.toml create mode 100644 xtask/agentic/ast_nodes.txt create mode 100644 xtask/agentic/src/claude_session.rs create mode 100644 xtask/agentic/src/lib.rs create mode 100644 xtask/agentic/src/main.rs create mode 100644 xtask/agentic/src/pretty_print.rs create mode 100644 xtask/src/agentic.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..5592118f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +xtask = "run -p xtask --" diff --git a/Cargo.lock b/Cargo.lock index 7c1b1cd8..b96955ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5665,6 +5665,19 @@ dependencies = [ "zip", ] +[[package]] +name = "xtask_agentic" +version = "0.0.0" +dependencies = [ + "anyhow", + "bpaf", + "pgt_pretty_print", + "pgt_query", + "serde", + "serde_json", + "xtask", +] + [[package]] name = "xtask_codegen" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 1f3d4eb9..17ead2a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/*", "lib/*", "xtask/codegen", "xtask/rules_check", "docs/codegen"] +members = ["crates/*", "lib/*", "xtask/codegen", "xtask/rules_check", "xtask/agentic", "docs/codegen"] resolver = "2" [workspace.package] diff --git a/crates/pgt_pretty_print/src/renderer.rs b/crates/pgt_pretty_print/src/renderer.rs index 03b460a4..33dd3e7f 100644 --- a/crates/pgt_pretty_print/src/renderer.rs +++ b/crates/pgt_pretty_print/src/renderer.rs @@ -216,7 +216,7 @@ impl Renderer { _ => {} } } - assert!(false, "Unmatched group start"); + panic!("Unmatched group start"); } fn write_text(&mut self, text: &str) -> Result<(), std::fmt::Error> { diff --git a/crates/pgt_pretty_print/tests/tests.rs b/crates/pgt_pretty_print/tests/tests.rs index 983ce9f2..73250e07 100644 --- a/crates/pgt_pretty_print/tests/tests.rs +++ b/crates/pgt_pretty_print/tests/tests.rs @@ -59,6 +59,27 @@ fn test_formatter(fixture: Fixture<&str>) { }); } +#[dir_test( + dir: "$CARGO_MANIFEST_DIR/tests/data/", + glob: "*.sql", +)] +fn validate_test_data(fixture: Fixture<&str>) { + let content = fixture.content(); + let absolute_fixture_path = Utf8Path::new(fixture.path()); + let test_name = absolute_fixture_path + .file_name() + .and_then(|x| x.strip_suffix(".sql")) + .unwrap(); + + let result = pgt_query::parse(content); + + if let Ok(res) = result.as_ref() { + assert!(res.root().is_some(), "should have a single root node"); + } + + assert!(result.is_ok(), "should be valid SQL"); +} + fn clear_location(node: &mut pgt_query::NodeEnum) { unsafe { node.iter_mut().for_each(|n| match n { diff --git a/justfile b/justfile index 7e53a8a6..ed2efcaa 100644 --- a/justfile +++ b/justfile @@ -30,6 +30,10 @@ gen-lint: cargo run -p docs_codegen just format +# Run the agentic loop with the specified name (e.g., just agentic pretty-print-impls) +agentic loop_name: + cargo xtask agentic {{loop_name}} + # Creates a new lint rule in the given path, with the given name. Name has to be camel case. Group should be lowercase. new-lintrule group rulename severity="error": cargo run -p xtask_codegen -- new-lintrule --category=lint --name={{rulename}} --group={{group}} --severity={{severity}} diff --git a/xtask/agentic/Cargo.toml b/xtask/agentic/Cargo.toml new file mode 100644 index 00000000..dfc4fe57 --- /dev/null +++ b/xtask/agentic/Cargo.toml @@ -0,0 +1,17 @@ +[package] +description = "AI-powered development tasks using Claude" +edition = "2021" +name = "xtask_agentic" +publish = false +version = "0.0.0" + +[dependencies] +anyhow = { workspace = true } +bpaf = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +xtask = { path = '../', version = "0.0" } + +# Pretty print specific dependencies +pgt_pretty_print = { workspace = true } +pgt_query = { workspace = true } diff --git a/xtask/agentic/ast_nodes.txt b/xtask/agentic/ast_nodes.txt new file mode 100644 index 00000000..69bd44bd --- /dev/null +++ b/xtask/agentic/ast_nodes.txt @@ -0,0 +1,271 @@ +InsertStmt +DeleteStmt +UpdateStmt +MergeStmt +SelectStmt +SetOperationStmt +ReturnStmt +PlAssignStmt +CreateSchemaStmt +AlterTableStmt +ReplicaIdentityStmt +AlterCollationStmt +AlterDomainStmt +GrantStmt +GrantRoleStmt +AlterDefaultPrivilegesStmt +CopyStmt +VariableSetStmt +VariableShowStmt +CreateStmt +CreateTableSpaceStmt +DropTableSpaceStmt +AlterTableSpaceOptionsStmt +AlterTableMoveAllStmt +CreateExtensionStmt +AlterExtensionStmt +AlterExtensionContentsStmt +CreateFdwStmt +AlterFdwStmt +CreateForeignServerStmt +AlterForeignServerStmt +CreateForeignTableStmt +CreateUserMappingStmt +AlterUserMappingStmt +DropUserMappingStmt +ImportForeignSchemaStmt +CreatePolicyStmt +AlterPolicyStmt +CreateAmStmt +CreateTrigStmt +CreateEventTrigStmt +AlterEventTrigStmt +CreatePLangStmt +CreateRoleStmt +AlterRoleStmt +AlterRoleSetStmt +DropRoleStmt +CreateSeqStmt +AlterSeqStmt +DefineStmt +CreateDomainStmt +CreateOpClassStmt +CreateOpFamilyStmt +AlterOpFamilyStmt +DropStmt +TruncateStmt +CommentStmt +SecLabelStmt +DeclareCursorStmt +ClosePortalStmt +FetchStmt +IndexStmt +CreateStatsStmt +AlterStatsStmt +CreateFunctionStmt +AlterFunctionStmt +DoStmt +CallStmt +RenameStmt +AlterObjectDependsStmt +AlterObjectSchemaStmt +AlterOwnerStmt +AlterOperatorStmt +AlterTypeStmt +RuleStmt +NotifyStmt +ListenStmt +UnlistenStmt +TransactionStmt +CompositeTypeStmt +CreateEnumStmt +CreateRangeStmt +AlterEnumStmt +ViewStmt +LoadStmt +CreatedbStmt +AlterDatabaseStmt +AlterDatabaseRefreshCollStmt +AlterDatabaseSetStmt +DropdbStmt +AlterSystemStmt +ClusterStmt +VacuumStmt +ExplainStmt +CreateTableAsStmt +RefreshMatViewStmt +CheckPointStmt +DiscardStmt +LockStmt +ConstraintsSetStmt +ReindexStmt +CreateConversionStmt +CreateCastStmt +CreateTransformStmt +PrepareStmt +ExecuteStmt +DeallocateStmt +DropOwnedStmt +ReassignOwnedStmt +AlterTsDictionaryStmt +AlterTsConfigurationStmt +CreatePublicationStmt +AlterPublicationStmt +CreateSubscriptionStmt +AlterSubscriptionStmt +DropSubscriptionStmt +ParseResult +ScanResult +Node +Integer +Float +Boolean +String +BitString +List +OidList +IntList +AConst +Alias +RangeVar +TableFunc +IntoClause +Var +Param +Aggref +GroupingFunc +WindowFunc +WindowFuncRunCondition +MergeSupportFunc +SubscriptingRef +FuncExpr +NamedArgExpr +OpExpr +DistinctExpr +NullIfExpr +ScalarArrayOpExpr +BoolExpr +SubLink +SubPlan +AlternativeSubPlan +FieldSelect +FieldStore +RelabelType +CoerceViaIo +ArrayCoerceExpr +ConvertRowtypeExpr +CollateExpr +CaseExpr +CaseWhen +CaseTestExpr +ArrayExpr +RowExpr +RowCompareExpr +CoalesceExpr +MinMaxExpr +SqlValueFunction +XmlExpr +JsonFormat +JsonReturning +JsonValueExpr +JsonConstructorExpr +JsonIsPredicate +JsonBehavior +JsonExpr +JsonTablePath +JsonTablePathScan +JsonTableSiblingJoin +NullTest +BooleanTest +MergeAction +CoerceToDomain +CoerceToDomainValue +SetToDefault +CurrentOfExpr +NextValueExpr +InferenceElem +TargetEntry +RangeTblRef +JoinExpr +FromExpr +OnConflictExpr +Query +TypeName +ColumnRef +ParamRef +AExpr +TypeCast +CollateClause +RoleSpec +FuncCall +AStar +AIndices +AIndirection +AArrayExpr +ResTarget +MultiAssignRef +SortBy +WindowDef +RangeSubselect +RangeFunction +RangeTableFunc +RangeTableFuncCol +RangeTableSample +ColumnDef +TableLikeClause +IndexElem +DefElem +LockingClause +XmlSerialize +PartitionElem +PartitionSpec +PartitionBoundSpec +PartitionRangeDatum +SinglePartitionSpec +PartitionCmd +RangeTblEntry +RtePermissionInfo +RangeTblFunction +TableSampleClause +WithCheckOption +SortGroupClause +GroupingSet +WindowClause +RowMarkClause +WithClause +InferClause +OnConflictClause +CteSearchClause +CteCycleClause +CommonTableExpr +MergeWhenClause +TriggerTransition +JsonOutput +JsonArgument +JsonFuncExpr +JsonTablePathSpec +JsonTable +JsonTableColumn +JsonKeyValue +JsonParseExpr +JsonScalarExpr +JsonSerializeExpr +JsonObjectConstructor +JsonArrayConstructor +JsonArrayQueryConstructor +JsonAggConstructor +JsonObjectAgg +JsonArrayAgg +AlterTableCmd +ObjectWithArgs +AccessPriv +Constraint +CreateOpClassItem +StatsElem +FunctionParameter +InlineCodeBlock +CallContext +VacuumRelation +PublicationTable +PublicationObjSpec +ScanToken \ No newline at end of file diff --git a/xtask/agentic/src/claude_session.rs b/xtask/agentic/src/claude_session.rs new file mode 100644 index 00000000..847efc82 --- /dev/null +++ b/xtask/agentic/src/claude_session.rs @@ -0,0 +1,112 @@ +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::process::Command; +use xtask::project_root; + +#[derive(Debug, Serialize, Deserialize)] +pub struct AgenticState { + pub current_node: String, + pub completed_nodes: Vec, + pub errors: Vec, + pub in_progress_nodes: HashMap, // node -> iteration count +} + +impl AgenticState { + pub fn new() -> Self { + Self { + current_node: String::new(), + completed_nodes: Vec::new(), + errors: Vec::new(), + in_progress_nodes: HashMap::new(), + } + } + + pub fn load() -> Result { + let path = project_root().join("xtask/agentic/agentic_state.json"); + if path.exists() { + let content = fs::read_to_string(path)?; + Ok(serde_json::from_str(&content)?) + } else { + Ok(Self::new()) + } + } + + pub fn save(&self) -> Result<()> { + let path = project_root().join("xtask/agentic/agentic_state.json"); + let content = serde_json::to_string_pretty(self)?; + fs::write(path, content)?; + Ok(()) + } +} + +pub struct ClaudeSession { + conversation_id: Option, +} + +impl ClaudeSession { + pub fn new() -> Self { + Self { + conversation_id: None, + } + } + + pub fn call_claude(&mut self, prompt: &str, new_conversation: bool) -> Result { + // Create a temporary file for the prompt + let temp_file = "/tmp/claude_prompt.txt"; + fs::write(temp_file, prompt)?; + + let mut cmd = Command::new("claude"); + cmd.arg("--print") // Non-interactive mode + .arg("--output-format") + .arg("json") + .env_remove("ANTHROPIC_API_KEY") // Unset API key to use CLI auth + .current_dir(project_root()); // Set working directory to monorepo root + + // Reuse conversation if we have one and not explicitly starting new + if !new_conversation && self.conversation_id.is_some() { + if let Some(ref conv_id) = self.conversation_id { + cmd.arg("--resume").arg(conv_id); + } + } + + // Read prompt from file and pass it as argument + let prompt = fs::read_to_string(temp_file)?; + cmd.arg(&prompt); + + println!( + "Calling Claude with prompt: {}", + &prompt.lines().next().unwrap_or("") + ); + + let output = cmd + .output() + .context("Failed to execute claude CLI. Make sure it's installed and in PATH")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow::anyhow!("Claude CLI failed: {}", stderr)); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + + // Parse JSON response to extract session ID and result + let json_response: serde_json::Value = serde_json::from_str(&stdout) + .context("Failed to parse JSON response from Claude CLI")?; + + // Extract session ID for future requests + if let Some(session_id) = json_response.get("session_id").and_then(|s| s.as_str()) { + self.conversation_id = Some(session_id.to_string()); + } + + // Extract the actual result content + let result = json_response + .get("result") + .and_then(|r| r.as_str()) + .unwrap_or("") + .to_string(); + + Ok(result) + } +} diff --git a/xtask/agentic/src/lib.rs b/xtask/agentic/src/lib.rs new file mode 100644 index 00000000..e2277205 --- /dev/null +++ b/xtask/agentic/src/lib.rs @@ -0,0 +1,19 @@ +pub mod claude_session; +pub mod pretty_print; + +use anyhow::Result; +use bpaf::Bpaf; + +#[derive(Debug, Clone, Bpaf)] +#[bpaf(options)] +pub enum AgenticCommand { + /// Generate ToTokens implementations for pretty printing using AI + #[bpaf(command("pretty-print-impls"))] + PrettyPrintImpls, +} + +pub fn run_agentic_task(cmd: AgenticCommand) -> Result<()> { + match cmd { + AgenticCommand::PrettyPrintImpls => pretty_print::run_pretty_print_generator(), + } +} diff --git a/xtask/agentic/src/main.rs b/xtask/agentic/src/main.rs new file mode 100644 index 00000000..b33c03b4 --- /dev/null +++ b/xtask/agentic/src/main.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use xtask_agentic::{run_agentic_task, AgenticCommand}; + +fn main() -> Result<()> { + // For now, just run the pretty print implementation + let cmd = AgenticCommand::PrettyPrintImpls; + run_agentic_task(cmd) +} diff --git a/xtask/agentic/src/pretty_print.rs b/xtask/agentic/src/pretty_print.rs new file mode 100644 index 00000000..addfab24 --- /dev/null +++ b/xtask/agentic/src/pretty_print.rs @@ -0,0 +1,842 @@ +use anyhow::Result; +use std::fs; +use std::process::Command; +use xtask::project_root; + +use crate::claude_session::{AgenticState, ClaudeSession}; + +pub fn run_pretty_print_generator() -> Result<()> { + println!("Starting agentic pretty print implementation generator..."); + + let mut state = AgenticState::load()?; + let mut claude_session = ClaudeSession::new(); + + 'outer_loop: loop { + // Step 1: Pick next node + let node = match pick_next_node(&state) { + Ok(n) => n, + Err(_) => { + println!("All nodes have been processed!"); + break; + } + }; + + println!("\n=== Processing node: {} ===", node); + state.current_node = node.clone(); + state.save()?; + + // Start a new conversation for each node to keep context clean + println!("Step 2: Analyzing node structure..."); + let node_info = check_node_data(&mut claude_session, &node)?; + println!("Node structure:\n{}", node_info); + + // Step 3: Generate test examples + println!("\nStep 3: Generating SQL examples..."); + let examples_result = generate_test_examples(&mut claude_session, &node, &node_info)?; + let parts: Vec<&str> = examples_result.split('|').collect(); + let (mut filename, mut examples) = if parts.len() == 2 { + (parts[0].to_string(), parts[1].to_string()) + } else { + ("unknown.sql".to_string(), examples_result.clone()) + }; + println!("Generated: {} with SQL: {}", filename, examples); + + // Track iteration count for this node + let iteration = state.in_progress_nodes.get(&node).cloned().unwrap_or(0); + println!("Implementation iteration: {}", iteration + 1); + + let mut retry_count = 0; + let max_retries = 3; + + loop { + // Step 4: Validate examples with AST analysis + println!("\nStep 4: Validating examples with AST analysis..."); + if !validate_with_ast_analysis(&examples, &node, &mut claude_session, &filename)? { + if retry_count >= max_retries { + println!( + "❌ STOPPING: Failed to generate valid examples after {} retries for {}.", + max_retries, node + ); + println!( + "This indicates a problem with the SQL generation or validation logic." + ); + state + .errors + .push(format!("{}: Failed to generate valid examples", node)); + state.save()?; + return Err(anyhow::anyhow!( + "Failed to validate SQL examples for {}", + node + )); + } + println!("Validation failed. Regenerating examples..."); + let examples_result = + generate_test_examples(&mut claude_session, &node, &node_info)?; + let parts: Vec<&str> = examples_result.split('|').collect(); + let (new_filename, new_examples) = if parts.len() == 2 { + (parts[0].to_string(), parts[1].to_string()) + } else { + ("unknown.sql".to_string(), examples_result.clone()) + }; + filename = new_filename; + examples = new_examples; + retry_count += 1; + continue; + } + + // Step 5: Implement ToTokens + println!("\nStep 5: Implementing ToTokens trait..."); + let implementation = + implement_to_tokens(&mut claude_session, &node, &node_info, &examples, iteration)?; + + // Append implementation to nodes.rs file and add to Node match statement + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let separator = format!("\n\n// Implementation for {}\n", node); + let impl_content = format!("{}{}{}\n", separator, implementation, ""); + + // Read existing content + let existing_content = fs::read_to_string(&nodes_file).unwrap_or_default(); + + // Add variant to Node match statement if it doesn't exist + let updated_content = add_node_variant_to_match(&existing_content, &node)?; + + // Append the new implementation + let final_content = format!("{}{}", updated_content, impl_content); + fs::write(&nodes_file, final_content)?; + println!( + "Implementation appended to: {} and added to Node match", + nodes_file.display() + ); + + // Step 6: Check compilation first + println!("\nStep 6: Checking compilation..."); + if !check_compilation()? { + println!("Compilation failed. Implementation has syntax errors."); + if retry_count >= max_retries { + println!( + "Compilation failed after {} retries. Asking Claude for decision...", + max_retries + ); + + let should_iterate = should_iterate_or_move_on( + &mut claude_session, + &node, + "Compilation failed", + iteration, + retry_count, + )?; + + if should_iterate { + println!( + "Claude decided to try another iteration for {} (iteration {})", + node, + iteration + 1 + ); + state.in_progress_nodes.insert(node.clone(), iteration + 1); + fs::write(&nodes_file, existing_content)?; + state.save()?; + continue 'outer_loop; + } else { + println!("Claude decided to move to next node after compilation failures"); + state.errors.push(format!( + "{}: Compilation failed after {} attempts (iteration {})", + node, + max_retries, + iteration + 1 + )); + break; + } + } + + println!( + "Retrying implementation due to compilation errors... (attempt {} of {})", + retry_count + 1, + max_retries + ); + fs::write(&nodes_file, existing_content)?; + retry_count += 1; + continue; + } + + // Step 7: Run formatter tests + println!("\nStep 7: Running formatter tests..."); + let (test_success, test_errors) = run_formatter_tests(&node, &filename)?; + if !test_success { + println!("Tests failed. Error output:\n{}", test_errors); + + // Extract missing nodes from the error and prioritize them + let missing_nodes = + suggest_missing_implementations(&mut claude_session, &node, &test_errors)?; + if !missing_nodes.is_empty() { + println!("Found missing node implementations: {:?}", missing_nodes); + + // Add these missing nodes to the front of our processing queue + // by marking them as high-priority in our state + for missing_node in &missing_nodes { + if !state.completed_nodes.contains(missing_node) + && !state.in_progress_nodes.contains_key(missing_node) + { + println!("Prioritizing {} for next implementation", missing_node); + state.in_progress_nodes.insert(missing_node.clone(), 0); + } + } + state.save()?; + } + + if retry_count >= max_retries { + println!( + "Tests failed after {} retries. Asking Claude for decision...", + max_retries + ); + + // Let Claude decide whether to iterate or move on + let should_iterate = should_iterate_or_move_on( + &mut claude_session, + &node, + &test_errors, + iteration, + retry_count, + )?; + + if should_iterate { + println!( + "Claude decided to try another iteration for {} (iteration {})", + node, + iteration + 1 + ); + state.in_progress_nodes.insert(node.clone(), iteration + 1); + fs::write(&nodes_file, existing_content)?; + state.save()?; + continue 'outer_loop; + } else { + println!( + "Claude decided to move to next node after {} failed attempts", + max_retries + ); + state.errors.push(format!( + "{}: Tests failed after {} attempts (iteration {})", + node, + max_retries, + iteration + 1 + )); + break; + } + } + + println!( + "Retrying implementation... (attempt {} of {})", + retry_count + 1, + max_retries + ); + + // Remove the failed implementation from nodes.rs + fs::write(&nodes_file, existing_content)?; + + retry_count += 1; + continue; + } + + // Step 8: Verify coverage + println!("\nStep 8: Verifying property coverage..."); + if !verify_node_coverage(&mut claude_session, &node, &implementation, &node_info)? { + println!("Warning: Not all properties are covered in the implementation!"); + + // Let Claude decide whether to improve coverage or move on + println!("Asking Claude whether to improve coverage..."); + let should_iterate = should_improve_coverage( + &mut claude_session, + &node, + &implementation, + &node_info, + iteration, + )?; + + if should_iterate { + println!("Claude decided to iterate to improve coverage for {}", node); + state.in_progress_nodes.insert(node.clone(), iteration + 1); + state.save()?; + continue 'outer_loop; + } else { + println!("Claude decided current coverage is sufficient for now"); + } + } + + // Success! + println!( + "\n✓ Successfully implemented {} (iteration {})", + node, + iteration + 1 + ); + state.completed_nodes.push(node.clone()); + state.in_progress_nodes.remove(&node); // Remove from in-progress + state.save()?; + break; + } + + // Auto-continue to next node + println!("\nAuto-continuing to next node..."); + } + + println!("\n=== Summary ==="); + println!("Completed nodes: {}", state.completed_nodes.len()); + println!("In-progress nodes: {}", state.in_progress_nodes.len()); + println!("Errors: {}", state.errors.len()); + + if !state.in_progress_nodes.is_empty() { + println!("\nNodes in progress (with iteration counts):"); + for (node, iteration) in &state.in_progress_nodes { + println!(" - {} (iteration {})", node, iteration); + } + } + + if !state.errors.is_empty() { + println!("\nNodes with errors:"); + for error in &state.errors { + println!(" - {}", error); + } + } + + Ok(()) +} + +fn pick_next_node(state: &AgenticState) -> Result { + // Read the AST nodes file from the xtask agentic crate + let ast_nodes_path = project_root().join("xtask/agentic/ast_nodes.txt"); + let ast_nodes = fs::read_to_string(ast_nodes_path)?; + let nodes: Vec<&str> = ast_nodes.lines().collect(); + + // Strategy: + // 1. First implement any in-progress nodes (nodes we started but haven't finished) + // 2. Then Stmt nodes (as they're the top-level constructs) + // 3. Then all other nodes + + // First pass: Look for in-progress nodes that aren't completed + for node in nodes.iter() { + let node_str = node.to_string(); + if state.in_progress_nodes.contains_key(&node_str) + && !state.completed_nodes.contains(&node_str) + { + return Ok(node_str); // Continue with in-progress work + } + } + + // Second pass: Look for incomplete Stmt nodes + for node in nodes.iter() { + let node_str = node.to_string(); + if !node_str.ends_with("Stmt") { + continue; // Skip non-Stmt nodes in this pass + } + + if state.completed_nodes.contains(&node_str) { + continue; // Already completed + } + + return Ok(node_str); + } + + // Third pass: Look for any incomplete node + for node in nodes.iter() { + let node_str = node.to_string(); + if state.completed_nodes.contains(&node_str) { + continue; // Already completed + } + + return Ok(node_str); + } + + Err(anyhow::anyhow!("All nodes have been processed")) +} + +fn suggest_missing_implementations( + session: &mut ClaudeSession, + node: &str, + error_output: &str, +) -> Result> { + // First try to extract node names directly from "not implemented" errors + let mut direct_suggestions = Vec::new(); + + // Look for patterns like "Node type AConst(..." in the error + for line in error_output.lines() { + if line.contains("not implemented for to_tokens") { + if let Some(start) = line.find("Node type ") { + if let Some(end) = line[start + 10..].find('(') { + let node_name = &line[start + 10..start + 10 + end]; + if !node_name.is_empty() && !direct_suggestions.contains(&node_name.to_string()) + { + direct_suggestions.push(node_name.to_string()); + } + } + } + } + } + + if !direct_suggestions.is_empty() { + println!("Found missing nodes from error: {:?}", direct_suggestions); + return Ok(direct_suggestions); + } + + // Fallback to Claude analysis + let prompt = format!( + "The ToTokens implementation for {} failed with this error:\n\ + {}\n\n\ + Extract the specific AST node types that are missing implementations.\n\ + Look for errors like 'Node type XYZ not implemented for to_tokens'.\n\ + Respond with one node type name per line, just the type name.\n\ + If no missing nodes found, respond with 'NONE'", + node, error_output + ); + + let response = session.call_claude(&prompt, false)?; + let suggestions: Vec = response + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty() && *line != "NONE") + .map(|s| s.to_string()) + .collect(); + + Ok(suggestions) +} + +fn should_iterate_or_move_on( + session: &mut ClaudeSession, + node: &str, + error_output: &str, + iteration: u32, + retry_count: u32, +) -> Result { + let prompt = format!( + "The ToTokens implementation for {} (iteration {}) failed after {} retries with this error:\n\ + {}\n\n\ + Should we try another iteration NOW, or move on and come back later?\n\ + Remember: ALL nodes eventually need FULL coverage. Moving on is just to make progress.\n\ + Consider:\n\ + - Does the error show we're blocked by missing node implementations?\n\ + - For complex nodes like SelectStmt, can we defer some features and implement dependent nodes first?\n\ + - Would implementing simpler nodes (like String, RangeVar) unblock this one?\n\ + \n\ + Example: SelectStmt can start with just 'SELECT 1' and add FROM/WHERE/etc later after implementing the nodes they depend on.\n\ + \n\ + Respond with ONLY 'ITERATE' to try another iteration now, or 'MOVE_ON' to implement dependencies first.", + node, iteration + 1, retry_count, error_output + ); + + let response = session.call_claude(&prompt, false)?; + Ok(response.trim() == "ITERATE") +} + +fn should_improve_coverage( + session: &mut ClaudeSession, + node: &str, + implementation: &str, + node_info: &str, + iteration: u32, +) -> Result { + let prompt = format!( + "The ToTokens implementation for {} (iteration {}) is working but doesn't cover all properties.\n\ + Implementation:\n{}\n\ + Node structure:\n{}\n\n\ + Should we add more properties NOW, or move on and come back later?\n\ + Remember: ALL nodes eventually need FULL coverage. This is about sequencing.\n\ + Consider:\n\ + - Do we have enough to unblock other nodes? (e.g., SelectStmt with just SELECT/FROM might be enough to test RangeVar)\n\ + - Are the missing properties blocking progress on other nodes?\n\ + - Would implementing the dependent nodes first make this node easier to complete?\n\ + \n\ + Example: SelectStmt with basic SELECT/FROM is enough to move on, add WHERE/GROUP BY/etc after implementing their dependencies.\n\ + \n\ + Respond with ONLY 'ITERATE' to add more properties now, or 'MOVE_ON' to come back later.", + node, iteration + 1, implementation, node_info + ); + + let response = session.call_claude(&prompt, false)?; + Ok(response.trim() == "ITERATE") +} + +fn check_node_data(session: &mut ClaudeSession, node: &str) -> Result { + let prompt = format!( + "Please analyze the struct {} in the file crates/pgt_query/src/protobuf.rs and list all its fields with their types. Format the response as a simple list.", + node + ); + + session.call_claude(&prompt, false) +} + +fn generate_test_examples( + session: &mut ClaudeSession, + node: &str, + node_info: &str, +) -> Result { + // Check if we have existing implementation and examples + let existing_impl = get_existing_implementation(node)?; + let existing_examples = get_existing_test_examples(node)?; + + // Calculate next test number + let next_test_number = existing_examples.len(); + let base_filename = node.to_lowercase().replace("stmt", "_stmt"); + let filename = format!("{}_{}_{}", base_filename, next_test_number, 80); + + let guidance = if existing_impl.is_none() && existing_examples.is_empty() { + // First time - generate minimal example + "Generate the SIMPLEST possible PostgreSQL SQL statement that would create this AST node.\n\ + Use the ABSOLUTE MINIMUM - just the essential keywords and one simple example.\n\ + For example:\n\ + - INSERT should be just 'INSERT INTO t VALUES (1)'\n\ + - SELECT should be just 'SELECT 1'\n\ + - UPDATE should be just 'UPDATE t SET c = 1'" + } else { + // We have existing work - generate example that exercises unimplemented fields + "Generate a PostgreSQL SQL statement that exercises the NEXT unimplemented feature.\n\ + Look at the existing implementation and examples to see what's already covered.\n\ + Generate SQL that would test a field or feature that's currently missing or marked with todo!().\n\ + Build incrementally - don't jump to the most complex example." + }; + + let context = if let Some(ref existing) = existing_impl { + format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) + } else { + String::new() + }; + + let examples_context = if !existing_examples.is_empty() { + format!( + "EXISTING TEST EXAMPLES:\n{}\n\n", + existing_examples.join("\n") + ) + } else { + String::new() + }; + + let prompt = format!( + "{}\n\n\ + {}{}AST STRUCTURE:\n{}\n\ + The test file will be named: {}.sql\n\ + \n\ + Generate only the SQL statement - no filename needed since it's predetermined.\n\ + Format your response as just:\n\ + SQL: your_sql_statement", + guidance, context, examples_context, node_info, filename + ); + + let response = session.call_claude(&prompt, false)?; + + // Parse SQL from response + let mut sql = String::new(); + + for line in response.lines() { + if let Some(statement) = line.strip_prefix("SQL: ") { + sql = statement.trim().to_string(); + break; + } + } + + // Fallback if parsing failed + if sql.is_empty() { + sql = response.trim().to_string(); + } + + // Use the predetermined filename + let final_filename = format!("{}.sql", filename); + + let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); + fs::create_dir_all(&test_dir)?; + let test_file = test_dir.join(&final_filename); + fs::write(&test_file, &sql)?; + println!( + "Wrote SQL to: {} with filename: {}", + test_file.display(), + final_filename + ); + + Ok(format!("{}|{}", final_filename, sql)) +} + +fn validate_single_file(filename: &str) -> Result { + // Use dir_test pattern - the test name is validate_test_data__ + filename without extension + let test_name_suffix = filename.replace(".sql", "").replace("-", "_"); + let full_test_name = format!("validate_test_data__{}", test_name_suffix); + + println!("Running specific validation test: {}", full_test_name); + + let output = Command::new("cargo") + .arg("test") + .arg("-p") + .arg("pgt_pretty_print") + .arg(&full_test_name) + .arg("--") + .arg("--nocapture") + .current_dir(project_root()) + .output()?; + + let success = output.status.success(); + + if !success { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + println!("Validation failed for {}:", filename); + println!("STDOUT:\n{}", stdout); + println!("STDERR:\n{}", stderr); + } else { + println!("✓ Validation passed for {}", filename); + } + + Ok(success) +} + +fn validate_with_ast_analysis( + sql: &str, + expected_node: &str, + session: &mut ClaudeSession, + filename: &str, +) -> Result { + // Step 1: Test the specific file we created + println!("Step 1: Running file-specific validation..."); + if !validate_single_file(filename)? { + return Ok(false); + } + + // Step 2: Ask Claude to analyze if the SQL would create the expected node + println!( + "Step 2: Getting Claude's analysis of SQL for {} node...", + expected_node + ); + let prompt = format!( + "I generated this SQL to create a {} AST node:\n{}\n\n\ + Does this SQL actually create a {} node when parsed by PostgreSQL? \ + Consider the SQL structure and what AST node type it would produce.\n\ + Reply with 'YES' if correct, or 'NO' with explanation and a corrected SQL example if wrong.", + expected_node, sql, expected_node + ); + + let response = session.call_claude(&prompt, false)?; + let is_correct = response.trim().starts_with("YES"); + + if !is_correct { + println!("Claude analysis: {}", response); + return Ok(false); + } + + println!("✓ All validation steps passed!"); + Ok(true) +} + +fn implement_to_tokens( + session: &mut ClaudeSession, + node: &str, + node_info: &str, + examples: &str, + iteration: u32, +) -> Result { + // Get existing implementation if it exists + let existing_impl = get_existing_implementation(node)?; + let trait_example = get_totokens_trait_example()?; + + let iteration_guidance = if iteration == 0 && existing_impl.is_none() { + "This is the FIRST implementation. Focus on the ABSOLUTE MINIMUM to make progress:\n\ + - Implement only the essential keywords that make the SQL valid\n\ + - Use todo!() for complex fields that aren't immediately needed\n\ + - Goal: Get something that compiles and handles the simplest test case" + } else if existing_impl.is_some() { + "This is an ITERATIVE IMPROVEMENT of an existing implementation.\n\ + - Analyze the existing implementation and the AST structure\n\ + - Identify which fields are NOT yet implemented (marked with todo!() or missing)\n\ + - Add support for the NEXT most important field that would handle more test cases\n\ + - Keep all existing working code intact" + } else { + &format!( + "This is iteration {} of the implementation. \ + Add more fields that are now unblocked because we've implemented their dependencies.", + iteration + 1 + ) + }; + + let context = if let Some(ref existing) = existing_impl { + format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) + } else { + String::new() + }; + + let prompt = format!( + "Implement the ToTokens trait for {} in Rust.\n\ + {}\n\n\ + TRAIT SIGNATURE EXAMPLE (use this exact signature):\n{}\n\n\ + {}AST STRUCTURE:\n{}\n\n\ + SQL EXAMPLES TO HANDLE:\n{}\n\n\ + INSTRUCTIONS:\n\ + - Use the EXACT trait signature shown above\n\ + - {} + - Generate ONLY the impl block, no imports or other code\n\ + - DO NOT use markdown code blocks - return plain Rust code\n\ + - Use existing TokenKind variants like INSERT_KW, INTO_KW, VALUES_KW, etc.", + node, + iteration_guidance, + trait_example, + context, + node_info, + examples, + if existing_impl.is_some() { + "EXTEND the existing implementation, don't replace it completely" + } else { + "Start minimal" + } + ); + + session.call_claude(&prompt, false) +} + +fn get_existing_implementation(node: &str) -> Result> { + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let content = fs::read_to_string(&nodes_file).unwrap_or_default(); + + // Look for existing implementation of this node + if let Some(start) = content.find(&format!("impl ToTokens for pgt_query::protobuf::{}", node)) { + if let Some(end) = content[start..].find("\n}\n") { + let impl_block = &content[start..start + end + 2]; + return Ok(Some(impl_block.to_string())); + } + } + + Ok(None) +} + +fn get_totokens_trait_example() -> Result { + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let content = fs::read_to_string(&nodes_file).unwrap_or_default(); + + // Extract an example implementation to show the correct signature + if let Some(start) = content.find("impl ToTokens for pgt_query::protobuf::SelectStmt") { + if let Some(end) = content[start..].find("}\n") { + let example = &content[start..start + end + 1]; + return Ok(example.to_string()); + } + } + + // Fallback if SelectStmt not found + Ok("impl ToTokens for pgt_query::protobuf::YourNode {\n fn to_tokens(&self, e: &mut EventEmitter) {\n e.token(TokenKind::YOUR_KW);\n }\n}".to_string()) +} + +fn get_existing_test_examples(node: &str) -> Result> { + let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); + let mut examples = Vec::new(); + + // Convert node name to expected filename pattern + let base_pattern = node.to_lowercase().replace("stmt", "_stmt"); + + if let Ok(entries) = fs::read_dir(&test_dir) { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { + // Check if this file matches our naming pattern: base_pattern_N_80.sql + if filename.starts_with(&format!("{}_", base_pattern)) + && filename.ends_with(".sql") + { + if let Ok(content) = fs::read_to_string(&path) { + examples.push(format!("{}: {}", filename, content.trim())); + } + } + } + } + } + } + + // Sort examples by number to maintain order + examples.sort(); + Ok(examples) +} + +fn add_node_variant_to_match(content: &str, node: &str) -> Result { + // Find the Node match statement and add our variant if it doesn't exist + let variant_pattern = format!("pgt_query::protobuf::node::Node::{}(", node); + + // If the variant already exists, return content unchanged + if content.contains(&variant_pattern) { + return Ok(content.to_string()); + } + + // Find the match statement ending with "_ => {" + if let Some(match_end) = content.find(" _ => {") { + // Insert our new variant before the default case + let before_default = &content[..match_end]; + let after_default = &content[match_end..]; + + let new_variant = format!( + " pgt_query::protobuf::node::Node::{}(node) => node.to_tokens(e),\n ", + node + ); + + let updated = format!("{}{}{}", before_default, new_variant, after_default); + println!("Added {} variant to Node match statement", node); + return Ok(updated); + } + + println!("Warning: Could not find Node match statement to update"); + Ok(content.to_string()) +} + +fn run_formatter_tests(_node: &str, filename: &str) -> Result<(bool, String)> { + // Run the specific formatter test for the node we just implemented + let test_name = format!("test_formatter__{}", filename.replace(".sql", "")); + + println!("Running test: {}", test_name); + + let output = Command::new("cargo") + .arg("test") + .arg("-p") + .arg("pgt_pretty_print") + .arg(&test_name) + .arg("--") + .arg("--nocapture") + .current_dir(project_root()) + .output()?; + + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + let success = output.status.success(); + let full_output = format!("STDOUT:\n{}\nSTDERR:\n{}", stdout, stderr); + + Ok((success, full_output)) +} + +fn check_compilation() -> Result { + println!("Running cargo check on pgt_pretty_print..."); + + let output = Command::new("cargo") + .arg("check") + .arg("-p") + .arg("pgt_pretty_print") + .current_dir(project_root()) + .output()?; + + let success = output.status.success(); + + if !success { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + println!("Compilation failed:"); + println!("STDOUT:\\n{}", stdout); + println!("STDERR:\\n{}", stderr); + } else { + println!("✓ Compilation successful"); + } + + Ok(success) +} + +fn verify_node_coverage( + session: &mut ClaudeSession, + node: &str, + implementation: &str, + node_info: &str, +) -> Result { + let prompt = format!( + "Verify that the following ToTokens implementation for {} covers all fields:\n\ + Implementation:\n{}\n\ + Node structure:\n{}\n\ + Reply with only 'YES' if all fields are covered, or 'NO' followed by missing fields.", + node, implementation, node_info + ); + + let response = session.call_claude(&prompt, false)?; + Ok(response.trim().starts_with("YES")) +} diff --git a/xtask/src/agentic.rs b/xtask/src/agentic.rs new file mode 100644 index 00000000..cf211b4f --- /dev/null +++ b/xtask/src/agentic.rs @@ -0,0 +1,14 @@ +use anyhow::Result; +use xshell::{cmd, Shell}; + +use crate::flags::{Agentic, AgenticCmd}; + +pub fn run(cmd: Agentic, sh: &Shell) -> Result<()> { + match cmd.subcommand { + AgenticCmd::PrettyPrintImpls(_) => { + println!("Running agentic pretty print implementation generator..."); + cmd!(sh, "cargo run -p xtask_agentic").run()?; + } + } + Ok(()) +} diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 4ed3da40..973334cc 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -18,6 +18,12 @@ xflags::xflags! { /// Install only the language server. optional --server } + + /// AI-powered development tasks using Claude. + cmd agentic { + /// Generate ToTokens implementations for pretty printing + cmd pretty-print-impls {} + } } } @@ -32,6 +38,7 @@ pub struct Xtask { #[derive(Debug)] pub enum XtaskCmd { Install(Install), + Agentic(Agentic), } #[derive(Debug)] @@ -41,6 +48,19 @@ pub struct Install { pub server: bool, } +#[derive(Debug)] +pub struct Agentic { + pub subcommand: AgenticCmd, +} + +#[derive(Debug)] +pub enum AgenticCmd { + PrettyPrintImpls(PrettyPrintImpls), +} + +#[derive(Debug)] +pub struct PrettyPrintImpls; + impl Xtask { #[allow(dead_code)] pub fn from_env_or_exit() -> Self { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 28247084..a75a4931 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -14,8 +14,8 @@ semicolon_in_expressions_from_macros )] +mod agentic; mod flags; - mod install; use std::{ @@ -32,6 +32,7 @@ fn main() -> anyhow::Result<()> { match flags.subcommand { flags::XtaskCmd::Install(cmd) => cmd.run(sh), + flags::XtaskCmd::Agentic(cmd) => agentic::run(cmd, sh), } } From 9f587b15e4a49304df54349d12eb7f6f9490df86 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Tue, 26 Aug 2025 10:39:15 +0200 Subject: [PATCH 10/15] progress --- xtask/agentic/src/pretty_print.rs | 48 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/xtask/agentic/src/pretty_print.rs b/xtask/agentic/src/pretty_print.rs index addfab24..0c580188 100644 --- a/xtask/agentic/src/pretty_print.rs +++ b/xtask/agentic/src/pretty_print.rs @@ -97,8 +97,9 @@ pub fn run_pretty_print_generator() -> Result<()> { // Read existing content let existing_content = fs::read_to_string(&nodes_file).unwrap_or_default(); - // Add variant to Node match statement if it doesn't exist - let updated_content = add_node_variant_to_match(&existing_content, &node)?; + // Ask Claude to add variant to Node match statement if needed + let updated_content = + update_node_match_with_claude(&mut claude_session, &existing_content, &node)?; // Append the new implementation let final_content = format!("{}{}", updated_content, impl_content); @@ -744,33 +745,38 @@ fn get_existing_test_examples(node: &str) -> Result> { Ok(examples) } -fn add_node_variant_to_match(content: &str, node: &str) -> Result { - // Find the Node match statement and add our variant if it doesn't exist +fn update_node_match_with_claude( + session: &mut ClaudeSession, + content: &str, + node: &str, +) -> Result { + // Check if the variant already exists let variant_pattern = format!("pgt_query::protobuf::node::Node::{}(", node); - - // If the variant already exists, return content unchanged if content.contains(&variant_pattern) { + println!("Node variant {} already exists in match statement", node); return Ok(content.to_string()); } - // Find the match statement ending with "_ => {" - if let Some(match_end) = content.find(" _ => {") { - // Insert our new variant before the default case - let before_default = &content[..match_end]; - let after_default = &content[match_end..]; + println!( + "Asking Claude to add {} variant to Node match statement", + node + ); - let new_variant = format!( - " pgt_query::protobuf::node::Node::{}(node) => node.to_tokens(e),\n ", - node - ); + let prompt = format!( + "I need to add a new variant to the Node match statement in this Rust code.\n\n\ + Current code:\n{}\n\n\ + Please add this variant to the match statement:\n\ + pgt_query::protobuf::node::Node::{}(node) => node.to_tokens(e),\n\n\ + Add it BEFORE the `_ => {{` default case.\n\ + Return the complete updated code with the new variant added.\n\ + DO NOT use markdown code blocks - return plain Rust code.", + content, node + ); - let updated = format!("{}{}{}", before_default, new_variant, after_default); - println!("Added {} variant to Node match statement", node); - return Ok(updated); - } + let response = session.call_claude(&prompt, false)?; + println!("Claude added {} variant to Node match statement", node); - println!("Warning: Could not find Node match statement to update"); - Ok(content.to_string()) + Ok(response) } fn run_formatter_tests(_node: &str, filename: &str) -> Result<(bool, String)> { From fbb05e5b9f1ec4ffab56ea733c3e4281c875fa89 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Wed, 27 Aug 2025 09:42:52 +0200 Subject: [PATCH 11/15] progress --- xtask/agentic/src/claude_session.rs | 46 +- xtask/agentic/src/pretty_print.rs | 1627 +++++++++++++++------------ 2 files changed, 933 insertions(+), 740 deletions(-) diff --git a/xtask/agentic/src/claude_session.rs b/xtask/agentic/src/claude_session.rs index 847efc82..f675e039 100644 --- a/xtask/agentic/src/claude_session.rs +++ b/xtask/agentic/src/claude_session.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fs; use std::process::Command; use xtask::project_root; @@ -11,6 +11,8 @@ pub struct AgenticState { pub completed_nodes: Vec, pub errors: Vec, pub in_progress_nodes: HashMap, // node -> iteration count + pub node_structure_cache: HashMap, // node -> structure analysis + pub node_match_updated: std::collections::HashSet, // nodes already added to match statement } impl AgenticState { @@ -20,6 +22,8 @@ impl AgenticState { completed_nodes: Vec::new(), errors: Vec::new(), in_progress_nodes: HashMap::new(), + node_structure_cache: HashMap::new(), + node_match_updated: HashSet::new(), } } @@ -39,6 +43,12 @@ impl AgenticState { fs::write(path, content)?; Ok(()) } + + pub fn clear_node_structure_cache(&mut self) -> Result<()> { + eprintln!("Clearing node structure cache (e.g., after protobuf changes)"); + self.node_structure_cache.clear(); + self.save() + } } pub struct ClaudeSession { @@ -53,16 +63,22 @@ impl ClaudeSession { } pub fn call_claude(&mut self, prompt: &str, new_conversation: bool) -> Result { - // Create a temporary file for the prompt - let temp_file = "/tmp/claude_prompt.txt"; - fs::write(temp_file, prompt)?; + use std::io::Write; + use std::process::Stdio; let mut cmd = Command::new("claude"); cmd.arg("--print") // Non-interactive mode .arg("--output-format") .arg("json") + .arg("--permission-mode") + .arg("acceptEdits") + .arg("--allowedTools") + .arg("Bash,Read,Write,Edit,MultiEdit,Grep,Glob,LS") .env_remove("ANTHROPIC_API_KEY") // Unset API key to use CLI auth - .current_dir(project_root()); // Set working directory to monorepo root + .current_dir(project_root()) // Set working directory to monorepo root + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); // Reuse conversation if we have one and not explicitly starting new if !new_conversation && self.conversation_id.is_some() { @@ -71,19 +87,25 @@ impl ClaudeSession { } } - // Read prompt from file and pass it as argument - let prompt = fs::read_to_string(temp_file)?; - cmd.arg(&prompt); - - println!( + eprintln!( "Calling Claude with prompt: {}", &prompt.lines().next().unwrap_or("") ); - let output = cmd - .output() + // Spawn the process + let mut child = cmd + .spawn() .context("Failed to execute claude CLI. Make sure it's installed and in PATH")?; + // Write prompt to stdin + if let Some(mut stdin) = child.stdin.take() { + stdin.write_all(prompt.as_bytes())?; + stdin.flush()?; + } + + // Wait for completion and collect output + let output = child.wait_with_output()?; + if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(anyhow::anyhow!("Claude CLI failed: {}", stderr)); diff --git a/xtask/agentic/src/pretty_print.rs b/xtask/agentic/src/pretty_print.rs index 0c580188..ee24d959 100644 --- a/xtask/agentic/src/pretty_print.rs +++ b/xtask/agentic/src/pretty_print.rs @@ -1,145 +1,165 @@ use anyhow::Result; use std::fs; +use std::io::Write; use std::process::Command; use xtask::project_root; use crate::claude_session::{AgenticState, ClaudeSession}; -pub fn run_pretty_print_generator() -> Result<()> { - println!("Starting agentic pretty print implementation generator..."); +struct Logger { + file: std::fs::File, +} - let mut state = AgenticState::load()?; - let mut claude_session = ClaudeSession::new(); +impl Logger { + fn new() -> Result { + let log_path = project_root().join("xtask/agentic/agent.log"); + let file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(log_path)?; + Ok(Self { file }) + } - 'outer_loop: loop { - // Step 1: Pick next node - let node = match pick_next_node(&state) { - Ok(n) => n, - Err(_) => { - println!("All nodes have been processed!"); - break; - } - }; + fn log(&mut self, message: &str) { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let log_line = format!("[{}] {}\n", timestamp, message); + let _ = self.file.write_all(log_line.as_bytes()); + let _ = self.file.flush(); + // Also print to console + print!("{}", log_line); + } +} - println!("\n=== Processing node: {} ===", node); - state.current_node = node.clone(); - state.save()?; - - // Start a new conversation for each node to keep context clean - println!("Step 2: Analyzing node structure..."); - let node_info = check_node_data(&mut claude_session, &node)?; - println!("Node structure:\n{}", node_info); - - // Step 3: Generate test examples - println!("\nStep 3: Generating SQL examples..."); - let examples_result = generate_test_examples(&mut claude_session, &node, &node_info)?; - let parts: Vec<&str> = examples_result.split('|').collect(); - let (mut filename, mut examples) = if parts.len() == 2 { - (parts[0].to_string(), parts[1].to_string()) - } else { - ("unknown.sql".to_string(), examples_result.clone()) - }; - println!("Generated: {} with SQL: {}", filename, examples); - - // Track iteration count for this node - let iteration = state.in_progress_nodes.get(&node).cloned().unwrap_or(0); - println!("Implementation iteration: {}", iteration + 1); - - let mut retry_count = 0; - let max_retries = 3; - - loop { - // Step 4: Validate examples with AST analysis - println!("\nStep 4: Validating examples with AST analysis..."); - if !validate_with_ast_analysis(&examples, &node, &mut claude_session, &filename)? { - if retry_count >= max_retries { - println!( - "❌ STOPPING: Failed to generate valid examples after {} retries for {}.", - max_retries, node - ); - println!( - "This indicates a problem with the SQL generation or validation logic." - ); - state - .errors - .push(format!("{}: Failed to generate valid examples", node)); - state.save()?; - return Err(anyhow::anyhow!( - "Failed to validate SQL examples for {}", - node - )); +pub struct PrettyPrintGenerator { + logger: Logger, + claude_session: ClaudeSession, + state: AgenticState, +} + +impl PrettyPrintGenerator { + pub fn new() -> Result { + let logger = Logger::new()?; + let claude_session = ClaudeSession::new(); + let state = AgenticState::load()?; + + Ok(Self { + logger, + claude_session, + state, + }) + } + + fn log(&mut self, message: &str) { + self.logger.log(message); + } + + pub fn run(&mut self) -> Result<()> { + self.log("Starting agentic pretty print implementation generator..."); + + 'outer_loop: loop { + // Step 1: Pick next node + let node = match self.pick_next_node() { + Ok(n) => n, + Err(_) => { + self.log("All nodes have been processed!"); + break; + } + }; + + self.log(&format!("\n=== Processing node: {} ===", node)); + self.state.current_node = node.clone(); + self.state.save()?; + + // Step 2: Get node structure (cached if available) + self.log("Step 2: Analyzing node structure..."); + let node_info = self.get_cached_node_structure(&node)?; + self.log(&format!("Node structure:\n{}", node_info)); + + // Step 3: Generate test examples + self.log("\nStep 3: Generating SQL examples..."); + let (mut filename, mut examples) = self.generate_test_examples(&node, &node_info)?; + self.log(&format!("Generated: {} with SQL: {}", filename, examples)); + + // Track iteration count for this node + let iteration = self + .state + .in_progress_nodes + .get(&node) + .cloned() + .unwrap_or(0); + self.log(&format!("Implementation iteration: {}", iteration + 1)); + + let mut retry_count = 0; + let max_retries = 3; + + loop { + // Step 4: Validate examples with AST analysis + self.log("\nStep 4: Validating examples with AST analysis..."); + if !self.validate_with_ast_analysis(&examples, &node, &filename)? { + if retry_count >= max_retries { + self.log(&format!( + "❌ STOPPING: Failed to generate valid examples after {} retries for {}.", + max_retries, node + )); + self.log( + "This indicates a problem with the SQL generation or validation logic.", + ); + self.state + .errors + .push(format!("{}: Failed to generate valid examples", node)); + self.state.save()?; + return Err(anyhow::anyhow!( + "Failed to validate SQL examples for {}", + node + )); + } + self.log("Validation failed. Regenerating examples..."); + let (new_filename, new_examples) = + self.generate_test_examples(&node, &node_info)?; + filename = new_filename; + examples = new_examples; + retry_count += 1; + continue; } - println!("Validation failed. Regenerating examples..."); - let examples_result = - generate_test_examples(&mut claude_session, &node, &node_info)?; - let parts: Vec<&str> = examples_result.split('|').collect(); - let (new_filename, new_examples) = if parts.len() == 2 { - (parts[0].to_string(), parts[1].to_string()) - } else { - ("unknown.sql".to_string(), examples_result.clone()) - }; - filename = new_filename; - examples = new_examples; - retry_count += 1; - continue; - } - // Step 5: Implement ToTokens - println!("\nStep 5: Implementing ToTokens trait..."); - let implementation = - implement_to_tokens(&mut claude_session, &node, &node_info, &examples, iteration)?; - - // Append implementation to nodes.rs file and add to Node match statement - let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); - let separator = format!("\n\n// Implementation for {}\n", node); - let impl_content = format!("{}{}{}\n", separator, implementation, ""); - - // Read existing content - let existing_content = fs::read_to_string(&nodes_file).unwrap_or_default(); - - // Ask Claude to add variant to Node match statement if needed - let updated_content = - update_node_match_with_claude(&mut claude_session, &existing_content, &node)?; - - // Append the new implementation - let final_content = format!("{}{}", updated_content, impl_content); - fs::write(&nodes_file, final_content)?; - println!( - "Implementation appended to: {} and added to Node match", - nodes_file.display() - ); - - // Step 6: Check compilation first - println!("\nStep 6: Checking compilation..."); - if !check_compilation()? { - println!("Compilation failed. Implementation has syntax errors."); - if retry_count >= max_retries { - println!( - "Compilation failed after {} retries. Asking Claude for decision...", - max_retries - ); + // Step 5: Implement ToTokens and update nodes.rs + self.log("\nStep 5: Implementing ToTokens trait and updating nodes.rs..."); + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); - let should_iterate = should_iterate_or_move_on( - &mut claude_session, + // Save current file content for rollback if needed + let existing_content = fs::read_to_string(&nodes_file).unwrap_or_default(); + + self.implement_to_tokens_and_update_file( + &node, + &node_info, + &examples, + iteration, + &nodes_file, + )?; + self.log("Implementation written to nodes.rs and Node match updated"); + + // Step 6: Check compilation first + self.log("\nStep 6: Checking compilation..."); + let compilation_result = self.check_compilation_with_errors()?; + if !compilation_result.0 { + self.log("Compilation failed. Implementation has syntax errors."); + + // For compilation errors, always ask Claude to fix them + self.log("Asking Claude to fix compilation errors..."); + self.fix_compilation_errors_directly( &node, - "Compilation failed", - iteration, - retry_count, + &nodes_file, + &compilation_result.1, )?; + self.log("Applied Claude's compilation fixes"); - if should_iterate { - println!( - "Claude decided to try another iteration for {} (iteration {})", - node, - iteration + 1 - ); - state.in_progress_nodes.insert(node.clone(), iteration + 1); - fs::write(&nodes_file, existing_content)?; - state.save()?; - continue 'outer_loop; - } else { - println!("Claude decided to move to next node after compilation failures"); - state.errors.push(format!( + retry_count += 1; + if retry_count >= max_retries { + self.log("Max retries reached for compilation fixes"); + self.state.errors.push(format!( "{}: Compilation failed after {} attempts (iteration {})", node, max_retries, @@ -147,702 +167,853 @@ pub fn run_pretty_print_generator() -> Result<()> { )); break; } + continue; } - println!( - "Retrying implementation due to compilation errors... (attempt {} of {})", - retry_count + 1, - max_retries - ); - fs::write(&nodes_file, existing_content)?; - retry_count += 1; - continue; - } + // Step 7: Run formatter tests + self.log("\nStep 7: Running formatter tests..."); + let (test_success, test_errors) = self.run_formatter_tests(&node, &filename)?; + if !test_success { + self.log(&format!("Tests failed. Error output:\n{}", test_errors)); + + // Check if this is a missing node error or an implementation issue + let missing_nodes = + self.extract_missing_node_types_with_claude(&test_errors)?; + if !missing_nodes.is_empty() { + self.log(&format!( + "Found missing node implementations: {:?}", + missing_nodes + )); - // Step 7: Run formatter tests - println!("\nStep 7: Running formatter tests..."); - let (test_success, test_errors) = run_formatter_tests(&node, &filename)?; - if !test_success { - println!("Tests failed. Error output:\n{}", test_errors); - - // Extract missing nodes from the error and prioritize them - let missing_nodes = - suggest_missing_implementations(&mut claude_session, &node, &test_errors)?; - if !missing_nodes.is_empty() { - println!("Found missing node implementations: {:?}", missing_nodes); - - // Add these missing nodes to the front of our processing queue - // by marking them as high-priority in our state - for missing_node in &missing_nodes { - if !state.completed_nodes.contains(missing_node) - && !state.in_progress_nodes.contains_key(missing_node) - { - println!("Prioritizing {} for next implementation", missing_node); - state.in_progress_nodes.insert(missing_node.clone(), 0); + // Add these missing nodes to the front of our processing queue + for missing_node in &missing_nodes { + if !self.state.completed_nodes.contains(missing_node) + && !self.state.in_progress_nodes.contains_key(missing_node) + { + self.log(&format!( + "Prioritizing {} for next implementation", + missing_node + )); + self.state.in_progress_nodes.insert(missing_node.clone(), 0); + } } + self.state.save()?; + } else { + // This is an implementation issue, not missing nodes + self.log( + "No missing nodes found. This appears to be an implementation issue.", + ); + self.log("The current implementation may need to be improved rather than adding new nodes."); + + // Ask Claude for guidance on the implementation issue + self.log("Getting Claude's analysis of the implementation issue..."); + let analysis = self.analyze_implementation_issue(&node, &test_errors)?; + self.log(&format!("Claude's analysis: {}", analysis)); + + // Ask Claude to fix the implementation based on the test failure + self.log("Asking Claude to fix the ToTokens implementation based on test failure..."); + self.fix_implementation_from_test_failure( + &node, + &nodes_file, + &test_errors, + &analysis, + )?; + self.log("Applied Claude's implementation fixes based on test failure"); } - state.save()?; - } - if retry_count >= max_retries { - println!( - "Tests failed after {} retries. Asking Claude for decision...", + if retry_count >= max_retries { + self.log(&format!( + "Tests failed after {} retries. Asking Claude for decision...", + max_retries + )); + + // Let Claude decide whether to iterate or move on + let should_iterate = self.should_iterate_or_move_on( + &node, + &test_errors, + iteration, + retry_count, + )?; + + if should_iterate { + self.log(&format!( + "Claude decided to try another iteration for {} (iteration {})", + node, + iteration + 1 + )); + self.state + .in_progress_nodes + .insert(node.clone(), iteration + 1); + fs::write(&nodes_file, existing_content)?; + self.state.save()?; + continue 'outer_loop; + } else { + self.log(&format!( + "Claude decided to move to next node after {} failed attempts", + max_retries + )); + self.state.errors.push(format!( + "{}: Tests failed after {} attempts (iteration {})", + node, + max_retries, + iteration + 1 + )); + break; + } + } + + self.log(&format!( + "Retrying implementation... (attempt {} of {})", + retry_count + 1, max_retries - ); + )); + + // Remove the failed implementation from nodes.rs + fs::write(&nodes_file, existing_content)?; + + retry_count += 1; + continue; + } - // Let Claude decide whether to iterate or move on - let should_iterate = should_iterate_or_move_on( - &mut claude_session, + // Step 8: Verify coverage + self.log("\nStep 8: Verifying property coverage..."); + if !self.verify_node_coverage_from_file(&node, &nodes_file, &node_info)? { + self.log("Warning: Not all properties are covered in the implementation!"); + + // Let Claude decide whether to improve coverage or move on + self.log("Asking Claude whether to improve coverage..."); + let should_iterate = self.should_improve_coverage_from_file( &node, - &test_errors, + &nodes_file, + &node_info, iteration, - retry_count, )?; if should_iterate { - println!( - "Claude decided to try another iteration for {} (iteration {})", - node, - iteration + 1 - ); - state.in_progress_nodes.insert(node.clone(), iteration + 1); - fs::write(&nodes_file, existing_content)?; - state.save()?; + self.log(&format!( + "Claude decided to iterate to improve coverage for {}", + node + )); + self.state + .in_progress_nodes + .insert(node.clone(), iteration + 1); + self.state.save()?; continue 'outer_loop; } else { - println!( - "Claude decided to move to next node after {} failed attempts", - max_retries - ); - state.errors.push(format!( - "{}: Tests failed after {} attempts (iteration {})", - node, - max_retries, - iteration + 1 - )); - break; + self.log("Claude decided current coverage is sufficient for now"); } } - println!( - "Retrying implementation... (attempt {} of {})", - retry_count + 1, - max_retries - ); - - // Remove the failed implementation from nodes.rs - fs::write(&nodes_file, existing_content)?; - - retry_count += 1; - continue; - } - - // Step 8: Verify coverage - println!("\nStep 8: Verifying property coverage..."); - if !verify_node_coverage(&mut claude_session, &node, &implementation, &node_info)? { - println!("Warning: Not all properties are covered in the implementation!"); - - // Let Claude decide whether to improve coverage or move on - println!("Asking Claude whether to improve coverage..."); - let should_iterate = should_improve_coverage( - &mut claude_session, - &node, - &implementation, - &node_info, - iteration, - )?; - - if should_iterate { - println!("Claude decided to iterate to improve coverage for {}", node); - state.in_progress_nodes.insert(node.clone(), iteration + 1); - state.save()?; - continue 'outer_loop; - } else { - println!("Claude decided current coverage is sufficient for now"); - } + // Success! + self.log(&format!( + "\n✓ Successfully implemented {} (iteration {})", + node, + iteration + 1 + )); + self.state.completed_nodes.push(node.clone()); + self.state.in_progress_nodes.remove(&node); // Remove from in-progress + self.state.save()?; + break; } - // Success! - println!( - "\n✓ Successfully implemented {} (iteration {})", - node, - iteration + 1 - ); - state.completed_nodes.push(node.clone()); - state.in_progress_nodes.remove(&node); // Remove from in-progress - state.save()?; - break; + // Auto-continue to next node + self.log("\nAuto-continuing to next node..."); } - // Auto-continue to next node - println!("\nAuto-continuing to next node..."); + self.print_summary(); + Ok(()) } - println!("\n=== Summary ==="); - println!("Completed nodes: {}", state.completed_nodes.len()); - println!("In-progress nodes: {}", state.in_progress_nodes.len()); - println!("Errors: {}", state.errors.len()); - - if !state.in_progress_nodes.is_empty() { - println!("\nNodes in progress (with iteration counts):"); - for (node, iteration) in &state.in_progress_nodes { - println!(" - {} (iteration {})", node, iteration); + fn print_summary(&mut self) { + self.log("\n=== Summary ==="); + self.log(&format!( + "Completed nodes: {}", + self.state.completed_nodes.len() + )); + self.log(&format!( + "In-progress nodes: {}", + self.state.in_progress_nodes.len() + )); + self.log(&format!("Errors: {}", self.state.errors.len())); + self.log(&format!( + "Cached node structures: {}", + self.state.node_structure_cache.len() + )); + self.log(&format!( + "Cached match statement updates: {}", + self.state.node_match_updated.len() + )); + + if !self.state.in_progress_nodes.is_empty() { + self.log("\nNodes in progress (with iteration counts):"); + let progress_nodes: Vec<_> = self + .state + .in_progress_nodes + .iter() + .map(|(k, v)| (k.clone(), *v)) + .collect(); + for (node, iteration) in progress_nodes { + self.log(&format!(" - {} (iteration {})", node, iteration)); + } } - } - if !state.errors.is_empty() { - println!("\nNodes with errors:"); - for error in &state.errors { - println!(" - {}", error); + if !self.state.errors.is_empty() { + self.log("\nNodes with errors:"); + let errors: Vec<_> = self.state.errors.clone(); + for error in errors { + self.log(&format!(" - {}", error)); + } } } - - Ok(()) } -fn pick_next_node(state: &AgenticState) -> Result { - // Read the AST nodes file from the xtask agentic crate - let ast_nodes_path = project_root().join("xtask/agentic/ast_nodes.txt"); - let ast_nodes = fs::read_to_string(ast_nodes_path)?; - let nodes: Vec<&str> = ast_nodes.lines().collect(); - - // Strategy: - // 1. First implement any in-progress nodes (nodes we started but haven't finished) - // 2. Then Stmt nodes (as they're the top-level constructs) - // 3. Then all other nodes - - // First pass: Look for in-progress nodes that aren't completed - for node in nodes.iter() { - let node_str = node.to_string(); - if state.in_progress_nodes.contains_key(&node_str) - && !state.completed_nodes.contains(&node_str) - { - return Ok(node_str); // Continue with in-progress work +impl PrettyPrintGenerator { + fn pick_next_node(&self) -> Result { + // Read the AST nodes file from the xtask agentic crate + let ast_nodes_path = project_root().join("xtask/agentic/ast_nodes.txt"); + let ast_nodes = fs::read_to_string(ast_nodes_path)?; + let nodes: Vec<&str> = ast_nodes.lines().collect(); + + // Strategy: + // 1. First implement any in-progress nodes (nodes we started but haven't finished) + // 2. Then Stmt nodes (as they're the top-level constructs) + // 3. Then all other nodes + + // First pass: Look for in-progress nodes that aren't completed + for node in nodes.iter() { + let node_str = node.to_string(); + if self.state.in_progress_nodes.contains_key(&node_str) + && !self.state.completed_nodes.contains(&node_str) + { + return Ok(node_str); // Continue with in-progress work + } } - } - // Second pass: Look for incomplete Stmt nodes - for node in nodes.iter() { - let node_str = node.to_string(); - if !node_str.ends_with("Stmt") { - continue; // Skip non-Stmt nodes in this pass + // Second pass: Look for incomplete Stmt nodes + for node in nodes.iter() { + let node_str = node.to_string(); + if !node_str.ends_with("Stmt") { + continue; // Skip non-Stmt nodes in this pass + } + + if self.state.completed_nodes.contains(&node_str) { + continue; // Already completed + } + + return Ok(node_str); } - if state.completed_nodes.contains(&node_str) { - continue; // Already completed + // Third pass: Look for any incomplete node + for node in nodes.iter() { + let node_str = node.to_string(); + if self.state.completed_nodes.contains(&node_str) { + continue; // Already completed + } + + return Ok(node_str); } - return Ok(node_str); + Err(anyhow::anyhow!("All nodes have been processed")) } - // Third pass: Look for any incomplete node - for node in nodes.iter() { - let node_str = node.to_string(); - if state.completed_nodes.contains(&node_str) { - continue; // Already completed + fn get_cached_node_structure(&mut self, node: &str) -> Result { + // Check cache first + if let Some(cached) = self.state.node_structure_cache.get(node).cloned() { + self.log(&format!("✓ Using cached node structure for {}", node)); + return Ok(cached); } - return Ok(node_str); - } + // Not cached, analyze with Claude + self.log(&format!( + "Analyzing {} structure with Claude (will cache for future use)", + node + )); + let node_info = self.check_node_data(node)?; - Err(anyhow::anyhow!("All nodes have been processed")) -} + // Cache the result + self.state + .node_structure_cache + .insert(node.to_string(), node_info.clone()); + self.state.save()?; -fn suggest_missing_implementations( - session: &mut ClaudeSession, - node: &str, - error_output: &str, -) -> Result> { - // First try to extract node names directly from "not implemented" errors - let mut direct_suggestions = Vec::new(); - - // Look for patterns like "Node type AConst(..." in the error - for line in error_output.lines() { - if line.contains("not implemented for to_tokens") { - if let Some(start) = line.find("Node type ") { - if let Some(end) = line[start + 10..].find('(') { - let node_name = &line[start + 10..start + 10 + end]; - if !node_name.is_empty() && !direct_suggestions.contains(&node_name.to_string()) - { - direct_suggestions.push(node_name.to_string()); - } - } - } - } + Ok(node_info) } - if !direct_suggestions.is_empty() { - println!("Found missing nodes from error: {:?}", direct_suggestions); - return Ok(direct_suggestions); + fn check_node_data(&mut self, node: &str) -> Result { + let prompt = format!( + "Please analyze the struct {} in the file crates/pgt_query/src/protobuf.rs and list all its fields with their types. Format the response as a simple list.", + node + ); + + self.claude_session.call_claude(&prompt, false) } - // Fallback to Claude analysis - let prompt = format!( - "The ToTokens implementation for {} failed with this error:\n\ - {}\n\n\ - Extract the specific AST node types that are missing implementations.\n\ - Look for errors like 'Node type XYZ not implemented for to_tokens'.\n\ - Respond with one node type name per line, just the type name.\n\ - If no missing nodes found, respond with 'NONE'", - node, error_output - ); - - let response = session.call_claude(&prompt, false)?; - let suggestions: Vec = response - .lines() - .map(|line| line.trim()) - .filter(|line| !line.is_empty() && *line != "NONE") - .map(|s| s.to_string()) - .collect(); - - Ok(suggestions) -} + fn generate_test_examples(&mut self, node: &str, node_info: &str) -> Result<(String, String)> { + // Check if we have existing implementation and examples + let existing_impl = self.get_existing_implementation(node)?; + let existing_examples = self.get_existing_test_examples(node)?; + + // Calculate next test number + let next_test_number = existing_examples.len(); + let base_filename = node.to_lowercase().replace("stmt", "_stmt"); + let filename = format!("{}_{}_{}", base_filename, next_test_number, 80); + + let guidance = if existing_impl.is_none() && existing_examples.is_empty() { + // First time - generate minimal example + "Generate the SIMPLEST possible PostgreSQL SQL statement that would create this AST node.\n\ + Use the ABSOLUTE MINIMUM - just the essential keywords and one simple example.\n\ + For example:\n\ + - INSERT should be just 'INSERT INTO t VALUES (1)'\n\ + - SELECT should be just 'SELECT 1'\n\ + - UPDATE should be just 'UPDATE t SET c = 1'" + } else { + // We have existing work - generate example that exercises unimplemented fields + "Generate a PostgreSQL SQL statement that exercises the NEXT unimplemented feature.\n\ + Look at the existing implementation and examples to see what's already covered.\n\ + Generate SQL that would test a field or feature that's currently missing or marked with todo!().\n\ + Build incrementally - don't jump to the most complex example." + }; -fn should_iterate_or_move_on( - session: &mut ClaudeSession, - node: &str, - error_output: &str, - iteration: u32, - retry_count: u32, -) -> Result { - let prompt = format!( - "The ToTokens implementation for {} (iteration {}) failed after {} retries with this error:\n\ - {}\n\n\ - Should we try another iteration NOW, or move on and come back later?\n\ - Remember: ALL nodes eventually need FULL coverage. Moving on is just to make progress.\n\ - Consider:\n\ - - Does the error show we're blocked by missing node implementations?\n\ - - For complex nodes like SelectStmt, can we defer some features and implement dependent nodes first?\n\ - - Would implementing simpler nodes (like String, RangeVar) unblock this one?\n\ - \n\ - Example: SelectStmt can start with just 'SELECT 1' and add FROM/WHERE/etc later after implementing the nodes they depend on.\n\ - \n\ - Respond with ONLY 'ITERATE' to try another iteration now, or 'MOVE_ON' to implement dependencies first.", - node, iteration + 1, retry_count, error_output - ); - - let response = session.call_claude(&prompt, false)?; - Ok(response.trim() == "ITERATE") -} + let context = if let Some(ref existing) = existing_impl { + format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) + } else { + String::new() + }; -fn should_improve_coverage( - session: &mut ClaudeSession, - node: &str, - implementation: &str, - node_info: &str, - iteration: u32, -) -> Result { - let prompt = format!( - "The ToTokens implementation for {} (iteration {}) is working but doesn't cover all properties.\n\ - Implementation:\n{}\n\ - Node structure:\n{}\n\n\ - Should we add more properties NOW, or move on and come back later?\n\ - Remember: ALL nodes eventually need FULL coverage. This is about sequencing.\n\ - Consider:\n\ - - Do we have enough to unblock other nodes? (e.g., SelectStmt with just SELECT/FROM might be enough to test RangeVar)\n\ - - Are the missing properties blocking progress on other nodes?\n\ - - Would implementing the dependent nodes first make this node easier to complete?\n\ - \n\ - Example: SelectStmt with basic SELECT/FROM is enough to move on, add WHERE/GROUP BY/etc after implementing their dependencies.\n\ - \n\ - Respond with ONLY 'ITERATE' to add more properties now, or 'MOVE_ON' to come back later.", - node, iteration + 1, implementation, node_info - ); - - let response = session.call_claude(&prompt, false)?; - Ok(response.trim() == "ITERATE") -} + let examples_context = if !existing_examples.is_empty() { + format!( + "EXISTING TEST EXAMPLES:\n{}\n\n", + existing_examples.join("\n") + ) + } else { + String::new() + }; -fn check_node_data(session: &mut ClaudeSession, node: &str) -> Result { - let prompt = format!( - "Please analyze the struct {} in the file crates/pgt_query/src/protobuf.rs and list all its fields with their types. Format the response as a simple list.", - node - ); + let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); + let final_filename = format!("{}.sql", filename); + let test_file = test_dir.join(&final_filename); + + let prompt = format!( + "{}\n\n\ + {}{}AST STRUCTURE:\n{}\n\ + \n\ + Generate a PostgreSQL SQL statement and write it to this file:\n\ + {}\n\ + \n\ + Use the Write tool to create the file with just the SQL statement (no extra text).\n\ + After writing, respond with just the SQL you wrote.", + guidance, + context, + examples_context, + node_info, + test_file.display() + ); + + let sql = self.claude_session.call_claude(&prompt, false)?; + let sql = sql.trim().to_string(); + + self.log(&format!( + "Claude wrote SQL to: {} with filename: {}", + test_file.display(), + final_filename + )); + + Ok((final_filename, sql)) + } - session.call_claude(&prompt, false) -} + fn validate_with_ast_analysis( + &mut self, + sql: &str, + expected_node: &str, + filename: &str, + ) -> Result { + // Step 1: Test the specific file we created + println!("Step 1: Running file-specific validation..."); + if !self.validate_single_file(filename)? { + return Ok(false); + } -fn generate_test_examples( - session: &mut ClaudeSession, - node: &str, - node_info: &str, -) -> Result { - // Check if we have existing implementation and examples - let existing_impl = get_existing_implementation(node)?; - let existing_examples = get_existing_test_examples(node)?; - - // Calculate next test number - let next_test_number = existing_examples.len(); - let base_filename = node.to_lowercase().replace("stmt", "_stmt"); - let filename = format!("{}_{}_{}", base_filename, next_test_number, 80); - - let guidance = if existing_impl.is_none() && existing_examples.is_empty() { - // First time - generate minimal example - "Generate the SIMPLEST possible PostgreSQL SQL statement that would create this AST node.\n\ - Use the ABSOLUTE MINIMUM - just the essential keywords and one simple example.\n\ - For example:\n\ - - INSERT should be just 'INSERT INTO t VALUES (1)'\n\ - - SELECT should be just 'SELECT 1'\n\ - - UPDATE should be just 'UPDATE t SET c = 1'" - } else { - // We have existing work - generate example that exercises unimplemented fields - "Generate a PostgreSQL SQL statement that exercises the NEXT unimplemented feature.\n\ - Look at the existing implementation and examples to see what's already covered.\n\ - Generate SQL that would test a field or feature that's currently missing or marked with todo!().\n\ - Build incrementally - don't jump to the most complex example." - }; - - let context = if let Some(ref existing) = existing_impl { - format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) - } else { - String::new() - }; - - let examples_context = if !existing_examples.is_empty() { - format!( - "EXISTING TEST EXAMPLES:\n{}\n\n", - existing_examples.join("\n") - ) - } else { - String::new() - }; - - let prompt = format!( - "{}\n\n\ - {}{}AST STRUCTURE:\n{}\n\ - The test file will be named: {}.sql\n\ - \n\ - Generate only the SQL statement - no filename needed since it's predetermined.\n\ - Format your response as just:\n\ - SQL: your_sql_statement", - guidance, context, examples_context, node_info, filename - ); - - let response = session.call_claude(&prompt, false)?; - - // Parse SQL from response - let mut sql = String::new(); - - for line in response.lines() { - if let Some(statement) = line.strip_prefix("SQL: ") { - sql = statement.trim().to_string(); - break; + // Step 2: Ask Claude to analyze if the SQL would create the expected node + println!( + "Step 2: Getting Claude's analysis of SQL for {} node...", + expected_node + ); + let prompt = format!( + "I generated this SQL to create a {} AST node:\n{}\n\n\ + Does this SQL actually create a {} node when parsed by PostgreSQL? \ + Consider the SQL structure and what AST node type it would produce.\n\ + Reply with 'YES' if correct, or 'NO' with explanation and a corrected SQL example if wrong.", + expected_node, sql, expected_node + ); + + let response = self.claude_session.call_claude(&prompt, false)?; + let is_correct = response.trim().starts_with("YES"); + + if !is_correct { + println!("Claude analysis: {}", response); + return Ok(false); } - } - // Fallback if parsing failed - if sql.is_empty() { - sql = response.trim().to_string(); + println!("✓ All validation steps passed!"); + Ok(true) } - // Use the predetermined filename - let final_filename = format!("{}.sql", filename); + fn validate_single_file(&mut self, filename: &str) -> Result { + // Use dir_test pattern - the test name is validate_test_data__ + filename without extension + let test_name_suffix = filename.replace(".sql", "").replace("-", "_"); + let full_test_name = format!("validate_test_data__{}", test_name_suffix); + + self.log(&format!( + "Running specific validation test: {}", + full_test_name + )); + + let output = Command::new("cargo") + .arg("test") + .arg("-p") + .arg("pgt_pretty_print") + .arg(&full_test_name) + .arg("--") + .arg("--nocapture") + .current_dir(project_root()) + .output()?; + + let success = output.status.success(); + + if !success { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + println!("Validation failed for {}:", filename); + println!("STDOUT:\n{}", stdout); + println!("STDERR:\n{}", stderr); + } else { + println!("✓ Validation passed for {}", filename); + } - let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); - fs::create_dir_all(&test_dir)?; - let test_file = test_dir.join(&final_filename); - fs::write(&test_file, &sql)?; - println!( - "Wrote SQL to: {} with filename: {}", - test_file.display(), - final_filename - ); + Ok(success) + } - Ok(format!("{}|{}", final_filename, sql)) -} + fn implement_to_tokens_and_update_file( + &mut self, + node: &str, + node_info: &str, + examples: &str, + iteration: u32, + nodes_file: &std::path::Path, + ) -> Result<()> { + // Get existing implementation if it exists + let existing_impl = self.get_existing_implementation(node)?; + let trait_example = self.get_totokens_trait_example()?; + + let iteration_guidance = if iteration == 0 && existing_impl.is_none() { + "This is the FIRST implementation. Focus on the ABSOLUTE MINIMUM to make progress:\n\ + - Implement only the essential keywords that make the SQL valid\n\ + - Use todo!() for complex fields that aren't immediately needed\n\ + - Goal: Get something that compiles and handles the simplest test case" + } else if existing_impl.is_some() { + "This is an ITERATIVE IMPROVEMENT of an existing implementation.\n\ + - Analyze the existing implementation and the AST structure\n\ + - Identify which fields are NOT yet implemented (marked with todo!() or missing)\n\ + - Add support for the NEXT most important field that would handle more test cases\n\ + - Keep all existing working code intact" + } else { + &format!( + "This is iteration {} of the implementation. \ + Add more fields that are now unblocked because we've implemented their dependencies.", + iteration + 1 + ) + }; -fn validate_single_file(filename: &str) -> Result { - // Use dir_test pattern - the test name is validate_test_data__ + filename without extension - let test_name_suffix = filename.replace(".sql", "").replace("-", "_"); - let full_test_name = format!("validate_test_data__{}", test_name_suffix); + let context = if let Some(ref existing) = existing_impl { + format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) + } else { + String::new() + }; + + // Check if we need to add the variant to the match statement + let needs_match_update = !self.state.node_match_updated.contains(node); + + let prompt = format!( + "Implement the ToTokens trait for {} in Rust and update nodes.rs file.\n\ + {}\n\n\ + FILE TO UPDATE: {}\n\n\ + TASKS:\n\ + 1. First use Read tool to read the current nodes.rs file\n\ + {}2. Add the ToTokens implementation at the end of the file\n\ + 3. Use Edit or MultiEdit to update the file\n\n\ + UNDERSTANDING THE TOTOKENS PATTERN:\n\ + The ToTokens trait converts AST nodes into formatting events for a pretty printer.\n\ + The EventEmitter records layout events that will later be rendered into formatted SQL.\n\n\ + EVENTEMIITTER API:\n\ + - e.token(TokenKind::KEYWORD) - Emit SQL keywords/symbols (INSERT_KW, COMMA, etc.)\n\ + - e.space() - Add a space\n\ + - e.line(LineType::SoftOrSpace) - Soft line break (becomes space if fits, newline if doesn't)\n\ + - e.line(LineType::Hard) - Force line break\n\ + - e.group_start(None, false) / e.group_end() - Logical formatting groups\n\ + - e.indent_start() / e.indent_end() - Control indentation\n\n\ + COMMON PATTERNS:\n\ + - Start with e.group_start(None, false) and end with e.group_end()\n\ + - Use e.space() between keywords: INSERT INTO becomes e.token(INSERT_KW); e.space(); e.token(INTO_KW)\n\ + - For optional fields: if let Some(field) = &self.field {{ field.to_tokens(e); }}\n\ + - For lists: iterate with commas between items\n\ + - Call .to_tokens(e) on child AST nodes to recursively format\n\n\ + TRAIT SIGNATURE EXAMPLE:\n{}\n\n\ + {}AST STRUCTURE:\n{}\n\n\ + SQL EXAMPLES TO HANDLE:\n{}\n\n\ + IMPLEMENTATION INSTRUCTIONS:\n\ + - Use the EXACT trait signature shown above\n\ + - {}\n\ + - DO NOT add any comments in the code\n\ + - Use existing TokenKind variants like INSERT_KW, INTO_KW, VALUES_KW, L_PAREN, R_PAREN, COMMA, etc.\n\ + - Add separator comment: // Implementation for {}\n\n\ + After updating the file, just respond 'Done'.", + node, + iteration_guidance, + nodes_file.display(), + if needs_match_update { + format!("2. Find the Node match statement and add this variant BEFORE the default case:\n pgt_query::protobuf::node::Node::{}(node) => node.to_tokens(e),\n", node) + } else { + "".to_string() + }, + trait_example, + context, + node_info, + examples, + if existing_impl.is_some() { + "EXTEND the existing implementation, don't replace it completely" + } else { + "Start minimal" + }, + node + ); + + self.claude_session.call_claude(&prompt, false)?; + + // Mark the node match as updated if needed + if needs_match_update { + self.state.node_match_updated.insert(node.to_string()); + self.state.save()?; + } - println!("Running specific validation test: {}", full_test_name); + Ok(()) + } - let output = Command::new("cargo") - .arg("test") - .arg("-p") - .arg("pgt_pretty_print") - .arg(&full_test_name) - .arg("--") - .arg("--nocapture") - .current_dir(project_root()) - .output()?; + fn check_compilation_with_errors(&self) -> Result<(bool, String)> { + println!("Running cargo check on pgt_pretty_print..."); - let success = output.status.success(); + let output = Command::new("cargo") + .arg("check") + .arg("-p") + .arg("pgt_pretty_print") + .current_dir(project_root()) + .output()?; - if !success { + let success = output.status.success(); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); - println!("Validation failed for {}:", filename); - println!("STDOUT:\n{}", stdout); - println!("STDERR:\n{}", stderr); - } else { - println!("✓ Validation passed for {}", filename); - } + let full_output = format!("STDOUT:\\n{}\\nSTDERR:\\n{}", stdout, stderr); - Ok(success) -} + if !success { + println!("Compilation failed:"); + println!("{}", full_output); + } else { + println!("✓ Compilation successful"); + } -fn validate_with_ast_analysis( - sql: &str, - expected_node: &str, - session: &mut ClaudeSession, - filename: &str, -) -> Result { - // Step 1: Test the specific file we created - println!("Step 1: Running file-specific validation..."); - if !validate_single_file(filename)? { - return Ok(false); + Ok((success, full_output)) } - // Step 2: Ask Claude to analyze if the SQL would create the expected node - println!( - "Step 2: Getting Claude's analysis of SQL for {} node...", - expected_node - ); - let prompt = format!( - "I generated this SQL to create a {} AST node:\n{}\n\n\ - Does this SQL actually create a {} node when parsed by PostgreSQL? \ - Consider the SQL structure and what AST node type it would produce.\n\ - Reply with 'YES' if correct, or 'NO' with explanation and a corrected SQL example if wrong.", - expected_node, sql, expected_node - ); - - let response = session.call_claude(&prompt, false)?; - let is_correct = response.trim().starts_with("YES"); - - if !is_correct { - println!("Claude analysis: {}", response); - return Ok(false); + fn fix_compilation_errors_directly( + &mut self, + node: &str, + nodes_file: &std::path::Path, + compilation_errors: &str, + ) -> Result<()> { + let prompt = format!( + "The ToTokens implementation for {} in {} has compilation errors.\\n\\n\ + COMPILATION ERRORS:\\n{}\\n\\n\ + Please:\\n\ + 1. Use Read to examine the file\\n\ + 2. Find the broken {} implementation\\n\ + 3. Use Edit to fix the compilation errors\\n\\n\ + Fix requirements:\\n\ + - Keep the same overall structure and logic\\n\ + - Fix syntax errors, type errors, missing imports, etc.\\n\ + - Preserve the rest of the file exactly\\n\\n\ + After fixing, just respond 'Fixed'.", + node, + nodes_file.display(), + compilation_errors, + node + ); + + self.claude_session.call_claude(&prompt, false)?; + Ok(()) } - println!("✓ All validation steps passed!"); - Ok(true) -} - -fn implement_to_tokens( - session: &mut ClaudeSession, - node: &str, - node_info: &str, - examples: &str, - iteration: u32, -) -> Result { - // Get existing implementation if it exists - let existing_impl = get_existing_implementation(node)?; - let trait_example = get_totokens_trait_example()?; - - let iteration_guidance = if iteration == 0 && existing_impl.is_none() { - "This is the FIRST implementation. Focus on the ABSOLUTE MINIMUM to make progress:\n\ - - Implement only the essential keywords that make the SQL valid\n\ - - Use todo!() for complex fields that aren't immediately needed\n\ - - Goal: Get something that compiles and handles the simplest test case" - } else if existing_impl.is_some() { - "This is an ITERATIVE IMPROVEMENT of an existing implementation.\n\ - - Analyze the existing implementation and the AST structure\n\ - - Identify which fields are NOT yet implemented (marked with todo!() or missing)\n\ - - Add support for the NEXT most important field that would handle more test cases\n\ - - Keep all existing working code intact" - } else { - &format!( - "This is iteration {} of the implementation. \ - Add more fields that are now unblocked because we've implemented their dependencies.", - iteration + 1 - ) - }; - - let context = if let Some(ref existing) = existing_impl { - format!("EXISTING IMPLEMENTATION:\n{}\n\n", existing) - } else { - String::new() - }; - - let prompt = format!( - "Implement the ToTokens trait for {} in Rust.\n\ - {}\n\n\ - TRAIT SIGNATURE EXAMPLE (use this exact signature):\n{}\n\n\ - {}AST STRUCTURE:\n{}\n\n\ - SQL EXAMPLES TO HANDLE:\n{}\n\n\ - INSTRUCTIONS:\n\ - - Use the EXACT trait signature shown above\n\ - - {} - - Generate ONLY the impl block, no imports or other code\n\ - - DO NOT use markdown code blocks - return plain Rust code\n\ - - Use existing TokenKind variants like INSERT_KW, INTO_KW, VALUES_KW, etc.", - node, - iteration_guidance, - trait_example, - context, - node_info, - examples, - if existing_impl.is_some() { - "EXTEND the existing implementation, don't replace it completely" - } else { - "Start minimal" - } - ); + fn run_formatter_tests(&self, _node: &str, filename: &str) -> Result<(bool, String)> { + // Run the specific formatter test for the node we just implemented + let test_name = format!("test_formatter__{}", filename.replace(".sql", "")); - session.call_claude(&prompt, false) -} + println!("Running test: {}", test_name); -fn get_existing_implementation(node: &str) -> Result> { - let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); - let content = fs::read_to_string(&nodes_file).unwrap_or_default(); + let output = Command::new("cargo") + .arg("test") + .arg("-p") + .arg("pgt_pretty_print") + .arg(&test_name) + .arg("--") + .arg("--nocapture") + .current_dir(project_root()) + .output()?; - // Look for existing implementation of this node - if let Some(start) = content.find(&format!("impl ToTokens for pgt_query::protobuf::{}", node)) { - if let Some(end) = content[start..].find("\n}\n") { - let impl_block = &content[start..start + end + 2]; - return Ok(Some(impl_block.to_string())); - } - } - - Ok(None) -} + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); -fn get_totokens_trait_example() -> Result { - let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); - let content = fs::read_to_string(&nodes_file).unwrap_or_default(); + let success = output.status.success(); + let full_output = format!("STDOUT:\n{}\nSTDERR:\n{}", stdout, stderr); - // Extract an example implementation to show the correct signature - if let Some(start) = content.find("impl ToTokens for pgt_query::protobuf::SelectStmt") { - if let Some(end) = content[start..].find("}\n") { - let example = &content[start..start + end + 1]; - return Ok(example.to_string()); - } + Ok((success, full_output)) } - // Fallback if SelectStmt not found - Ok("impl ToTokens for pgt_query::protobuf::YourNode {\n fn to_tokens(&self, e: &mut EventEmitter) {\n e.token(TokenKind::YOUR_KW);\n }\n}".to_string()) -} - -fn get_existing_test_examples(node: &str) -> Result> { - let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); - let mut examples = Vec::new(); - - // Convert node name to expected filename pattern - let base_pattern = node.to_lowercase().replace("stmt", "_stmt"); - - if let Ok(entries) = fs::read_dir(&test_dir) { - for entry in entries { - if let Ok(entry) = entry { - let path = entry.path(); - if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { - // Check if this file matches our naming pattern: base_pattern_N_80.sql - if filename.starts_with(&format!("{}_", base_pattern)) - && filename.ends_with(".sql") - { - if let Ok(content) = fs::read_to_string(&path) { - examples.push(format!("{}: {}", filename, content.trim())); - } - } - } - } + fn extract_missing_node_types_with_claude( + &mut self, + error_output: &str, + ) -> Result> { + let prompt = format!( + "Analyze this Rust compiler/test error output and extract ONLY missing AST node type names:\n\n\ + {}\n\n\ + Look for errors that indicate missing ToTokens implementations, typically:\n\ + - \"Node type XYZ not implemented for to_tokens\"\n\ + - unimplemented!() panics mentioning specific node types\n\ + \n\ + Valid AST node names are like: AConst, List, Integer, SelectStmt, InsertStmt, String, etc.\n\ + \n\ + If you find missing node types, respond with just the node names, one per line:\n\ + NodeName1\n\ + NodeName2\n\ + \n\ + If this is NOT about missing node implementations (e.g., it's about incorrect output format, \ + AST differences, etc.), respond with:\n\ + IMPLEMENTATION_ISSUE\n\ + \n\ + Do not include explanations or other text.", + error_output + ); + + let response = self.claude_session.call_claude(&prompt, false)?; + let response = response.trim(); + + if response == "IMPLEMENTATION_ISSUE" { + self.log("Claude identified this as an implementation issue, not missing nodes"); + return Ok(Vec::new()); } + + let missing_nodes: Vec = response + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty()) + .map(|s| s.to_string()) + .collect(); + + self.log(&format!( + "Claude extracted missing node types: {:?}", + missing_nodes + )); + Ok(missing_nodes) } - // Sort examples by number to maintain order - examples.sort(); - Ok(examples) -} + fn analyze_implementation_issue(&mut self, node: &str, error_output: &str) -> Result { + let prompt = format!( + "The ToTokens implementation for {} is failing tests. The test works by:\n\ + 1. Parse SQL to AST\n\ + 2. Format AST back to SQL using ToTokens\n\ + 3. Parse the formatted SQL to new AST\n\ + 4. Compare original AST with new AST\n\n\ + TEST ERROR:\n{}\n\n\ + The error shows 'left' (original AST) vs 'right' (formatted->parsed AST). \ + Look at the differences between left and right ASTs.\n\n\ + This means the ToTokens implementation is NOT outputting certain fields properly, \ + causing the round-trip to lose data.\n\n\ + Analyze WHICH FIELDS are missing in the 'right' AST compared to 'left' AST. \ + These are the fields the ToTokens implementation needs to handle.\n\n\ + Focus on specific missing fields like values_lists, target_list, etc.", + node, error_output + ); + + self.claude_session.call_claude(&prompt, false) + } -fn update_node_match_with_claude( - session: &mut ClaudeSession, - content: &str, - node: &str, -) -> Result { - // Check if the variant already exists - let variant_pattern = format!("pgt_query::protobuf::node::Node::{}(", node); - if content.contains(&variant_pattern) { - println!("Node variant {} already exists in match statement", node); - return Ok(content.to_string()); + fn should_iterate_or_move_on( + &mut self, + node: &str, + error_output: &str, + iteration: u32, + retry_count: u32, + ) -> Result { + let prompt = format!( + "The ToTokens implementation for {} (iteration {}) failed after {} retries with this error:\n\ + {}\n\n\ + Should we try another iteration NOW, or move on and come back later?\n\ + Remember: ALL nodes eventually need FULL coverage. Moving on is just to make progress.\n\ + Consider:\n\ + - Does the error show we're blocked by missing node implementations?\n\ + - For complex nodes like SelectStmt, can we defer some features and implement dependent nodes first?\n\ + - Would implementing simpler nodes (like String, RangeVar) unblock this one?\n\ + \n\ + Example: SelectStmt can start with just 'SELECT 1' and add FROM/WHERE/etc later after implementing the nodes they depend on.\n\ + \n\ + Respond with ONLY 'ITERATE' to try another iteration now, or 'MOVE_ON' to implement dependencies first.", + node, iteration + 1, retry_count, error_output + ); + + let response = self.claude_session.call_claude(&prompt, false)?; + Ok(response.trim() == "ITERATE") } - println!( - "Asking Claude to add {} variant to Node match statement", - node - ); - - let prompt = format!( - "I need to add a new variant to the Node match statement in this Rust code.\n\n\ - Current code:\n{}\n\n\ - Please add this variant to the match statement:\n\ - pgt_query::protobuf::node::Node::{}(node) => node.to_tokens(e),\n\n\ - Add it BEFORE the `_ => {{` default case.\n\ - Return the complete updated code with the new variant added.\n\ - DO NOT use markdown code blocks - return plain Rust code.", - content, node - ); - - let response = session.call_claude(&prompt, false)?; - println!("Claude added {} variant to Node match statement", node); - - Ok(response) -} + fn should_improve_coverage_from_file( + &mut self, + node: &str, + nodes_file: &std::path::Path, + node_info: &str, + iteration: u32, + ) -> Result { + let prompt = format!( + "The ToTokens implementation for {} (iteration {}) in {} is working but doesn't cover all properties.\n\ + Node structure:\n{}\n\n\ + Should we add more properties NOW, or move on and come back later?\n\n\ + Remember: ALL nodes eventually need FULL coverage. This is about sequencing.\n\n\ + Consider:\n\ + - Do we have enough to unblock other nodes? (e.g., SelectStmt with just SELECT/FROM might be enough to test RangeVar)\n\ + - Are the missing properties blocking progress on other nodes?\n\ + - Would implementing the dependent nodes first make this node easier to complete?\n\ + \n\ + Example: SelectStmt with basic SELECT/FROM is enough to move on, add WHERE/GROUP BY/etc after implementing their dependencies.\n\ + \n\ + Respond with ONLY 'ITERATE' to add more properties now, or 'MOVE_ON' to come back later.", + node, iteration + 1, nodes_file.display(), node_info + ); + + let response = self.claude_session.call_claude(&prompt, false)?; + Ok(response.trim() == "ITERATE") + } -fn run_formatter_tests(_node: &str, filename: &str) -> Result<(bool, String)> { - // Run the specific formatter test for the node we just implemented - let test_name = format!("test_formatter__{}", filename.replace(".sql", "")); + fn verify_node_coverage_from_file( + &mut self, + node: &str, + nodes_file: &std::path::Path, + node_info: &str, + ) -> Result { + let prompt = format!( + "Verify that the ToTokens implementation for {} in {} covers all fields.\n\n\ + Node structure:\n{}\n\n\ + Steps:\n\ + 1. Use Read to find and examine the {} implementation\n\ + 2. Check if all fields from the node structure are handled\n\n\ + Reply with only 'YES' if all fields are covered, or 'NO' followed by missing fields.", + node, + nodes_file.display(), + node_info, + node + ); + + let response = self.claude_session.call_claude(&prompt, false)?; + Ok(response.trim().starts_with("YES")) + } - println!("Running test: {}", test_name); + fn fix_implementation_from_test_failure( + &mut self, + node: &str, + nodes_file: &std::path::Path, + test_errors: &str, + analysis: &str, + ) -> Result<()> { + let prompt = format!( + "The ToTokens implementation for {} in {} is failing tests.\\n\\n\ + TEST FAILURE:\\n{}\\n\\n\ + ANALYSIS OF THE ISSUE:\\n{}\\n\\n\ + The test compares original AST with formatted->parsed AST. The 'right' AST is missing fields \ + that the 'left' AST has, meaning your ToTokens implementation isn't outputting those fields properly.\\n\\n\ + Please:\\n\ + 1. Use Read to examine the current {} implementation in the file\\n\ + 2. Identify which fields from the AST structure are not being handled\\n\ + 3. Use Edit to improve the ToTokens implementation to handle the missing fields\\n\ + 4. Focus on fields that appear in 'left' but are empty/missing in 'right'\\n\\n\ + For example, if left has `values_lists: [...]` but right has `values_lists: []`, \ + then you need to add code to handle the values_lists field in your ToTokens implementation.\\n\\n\ + After fixing, just respond 'Fixed'.", + node, nodes_file.display(), test_errors, analysis, node + ); + + self.claude_session.call_claude(&prompt, false)?; + Ok(()) + } - let output = Command::new("cargo") - .arg("test") - .arg("-p") - .arg("pgt_pretty_print") - .arg(&test_name) - .arg("--") - .arg("--nocapture") - .current_dir(project_root()) - .output()?; + fn get_existing_implementation(&self, node: &str) -> Result> { + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let content = fs::read_to_string(&nodes_file).unwrap_or_default(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); + // Look for existing implementation of this node + if let Some(start) = + content.find(&format!("impl ToTokens for pgt_query::protobuf::{}", node)) + { + if let Some(end) = content[start..].find("\n}\n") { + let impl_block = &content[start..start + end + 2]; + return Ok(Some(impl_block.to_string())); + } + } - let success = output.status.success(); - let full_output = format!("STDOUT:\n{}\nSTDERR:\n{}", stdout, stderr); + Ok(None) + } - Ok((success, full_output)) -} + fn get_totokens_trait_example(&self) -> Result { + let nodes_file = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let content = fs::read_to_string(&nodes_file).unwrap_or_default(); -fn check_compilation() -> Result { - println!("Running cargo check on pgt_pretty_print..."); + // Extract an example implementation to show the correct signature + if let Some(start) = content.find("impl ToTokens for pgt_query::protobuf::SelectStmt") { + if let Some(end) = content[start..].find("}\n") { + let example = &content[start..start + end + 1]; + return Ok(example.to_string()); + } + } - let output = Command::new("cargo") - .arg("check") - .arg("-p") - .arg("pgt_pretty_print") - .current_dir(project_root()) - .output()?; + // Fallback if SelectStmt not found + Ok("impl ToTokens for pgt_query::protobuf::YourNode {\n fn to_tokens(&self, e: &mut EventEmitter) {\n e.token(TokenKind::YOUR_KW);\n }\n}".to_string()) + } - let success = output.status.success(); + fn get_existing_test_examples(&self, node: &str) -> Result> { + let test_dir = project_root().join("crates/pgt_pretty_print/tests/data"); + let mut examples = Vec::new(); + + // Convert node name to expected filename pattern + let base_pattern = node.to_lowercase().replace("stmt", "_stmt"); + + if let Ok(entries) = fs::read_dir(&test_dir) { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { + // Check if this file matches our naming pattern: base_pattern_N_80.sql + if filename.starts_with(&format!("{}_", base_pattern)) + && filename.ends_with(".sql") + { + if let Ok(content) = fs::read_to_string(&path) { + examples.push(format!("{}: {}", filename, content.trim())); + } + } + } + } + } + } - if !success { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - println!("Compilation failed:"); - println!("STDOUT:\\n{}", stdout); - println!("STDERR:\\n{}", stderr); - } else { - println!("✓ Compilation successful"); + // Sort examples by number to maintain order + examples.sort(); + Ok(examples) } - - Ok(success) } -fn verify_node_coverage( - session: &mut ClaudeSession, - node: &str, - implementation: &str, - node_info: &str, -) -> Result { - let prompt = format!( - "Verify that the following ToTokens implementation for {} covers all fields:\n\ - Implementation:\n{}\n\ - Node structure:\n{}\n\ - Reply with only 'YES' if all fields are covered, or 'NO' followed by missing fields.", - node, implementation, node_info - ); - - let response = session.call_claude(&prompt, false)?; - Ok(response.trim().starts_with("YES")) +pub fn run_pretty_print_generator() -> Result<()> { + let mut generator = PrettyPrintGenerator::new()?; + generator.run() } From 166e9bbca7c8272abc4d1f007bda5a55c0600d59 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 28 Aug 2025 08:39:16 +0200 Subject: [PATCH 12/15] progress --- xtask/agentic/src/autonomous_pretty_print.rs | 227 +++++++++++++++++++ xtask/agentic/src/lib.rs | 7 + xtask/agentic/src/main.rs | 10 +- xtask/agentic/src/pretty_print.rs | 45 ++-- xtask/src/agentic.rs | 4 + xtask/src/flags.rs | 6 + 6 files changed, 277 insertions(+), 22 deletions(-) create mode 100644 xtask/agentic/src/autonomous_pretty_print.rs diff --git a/xtask/agentic/src/autonomous_pretty_print.rs b/xtask/agentic/src/autonomous_pretty_print.rs new file mode 100644 index 00000000..2f4a5b26 --- /dev/null +++ b/xtask/agentic/src/autonomous_pretty_print.rs @@ -0,0 +1,227 @@ +use anyhow::Result; +use std::fs; +use std::io::Write; +use xtask::project_root; + +use crate::claude_session::{AgenticState, ClaudeSession}; + +struct Logger { + file: std::fs::File, +} + +impl Logger { + fn new() -> Result { + let log_path = project_root().join("xtask/agentic/autonomous_agent.log"); + let file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(log_path)?; + Ok(Self { file }) + } + + fn log(&mut self, message: &str) { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let log_line = format!("[{}] {}\n", timestamp, message); + let _ = self.file.write_all(log_line.as_bytes()); + let _ = self.file.flush(); + // Also print to console + print!("{}", log_line); + } +} + +pub struct AutonomousPrettyPrintGenerator { + logger: Logger, + claude_session: ClaudeSession, + state: AgenticState, +} + +impl AutonomousPrettyPrintGenerator { + pub fn new() -> Result { + let logger = Logger::new()?; + let claude_session = ClaudeSession::new(); + let state = AgenticState::load()?; + + Ok(Self { + logger, + claude_session, + state, + }) + } + + fn log(&mut self, message: &str) { + self.logger.log(message); + } + + pub fn run(&mut self) -> Result<()> { + self.log("Starting autonomous pretty print implementation generator..."); + + loop { + self.log("Starting new autonomous cycle..."); + + let prompt = self.build_comprehensive_prompt()?; + + self.log("Sending comprehensive prompt to Claude..."); + let response = self.claude_session.call_claude(&prompt, false)?; + + self.log(&format!("Claude response: {}", response)); + + // Check if Claude indicates it's done + if response.contains("ALL_NODES_COMPLETED") { + self.log("Claude reports all nodes are completed!"); + break; + } + + // Small delay to prevent overwhelming + std::thread::sleep(std::time::Duration::from_secs(2)); + } + + self.log("Autonomous pretty print generator completed!"); + Ok(()) + } + + fn build_comprehensive_prompt(&mut self) -> Result { + let ast_nodes_list = self.load_ast_nodes_list()?; + let nodes_file_path = project_root().join("crates/pgt_pretty_print/src/nodes.rs"); + let test_data_dir = project_root().join("crates/pgt_pretty_print/tests/data"); + + let prompt = format!( + r#"You are an autonomous Rust code generator building a PostgreSQL pretty printer by implementing ToTokens traits for AST nodes. + +## PROJECT CONTEXT: +This is a PostgreSQL pretty printer that takes parsed AST nodes and converts them back into well-formatted SQL code. Each AST node type needs a ToTokens implementation that defines how to reconstruct readable SQL from the parsed structure. + +Your goal is to continuously implement ToTokens for all unimplemented nodes until the pretty printer is complete. + +## YOUR INFINITE LOOP TASK: + +1. **Read the current nodes.rs file** to see what's already implemented +2. **Pick the next unimplemented node** from the AST nodes list +3. **Analyze the node structure** in crates/pgt_query/src/protobuf.rs +4. **Generate a minimal SQL test case** and write it to tests/data/ +5. **Validate the SQL example** by running: cargo test -p pgt_pretty_print --test tests validate_test_data__[filename] +6. **Implement the ToTokens trait** for the node +7. **Add the node to the match statement** if not already there +8. **Run compilation checks** with cargo check -p pgt_pretty_print +9. **Run the formatter tests** with cargo test -p pgt_pretty_print +10. **Validate AST round-trip** - Ensure the formatted SQL parses back to the same AST structure +11. **Check output snapshot** - Read the generated .snap file to verify the formatted output looks correct +12. **Fix any issues** that arise +13. **Repeat from step 1** until all nodes are implemented + +## CRITICAL RULES: + +- **NEVER EVER add comments to Rust code** - ZERO // comments, ZERO /* */ comments in implementations +- **Only add this separator**: // Implementation for NodeName +- **NEVER manually implement nested nodes** - Always call .to_tokens(e) on child nodes, never implement their logic +- **Use existing ToTokens implementations** - If a node already has ToTokens, call it, don't reimplement +- **Start with simplest possible implementations** - use todo!() for complex fields initially +- **Incremental improvement** - come back to nodes later to add more fields +- **When tests fail**, analyze the AST diff and fix the missing ToTokens fields +- **Prioritize Stmt nodes first** (SelectStmt, InsertStmt, etc.) + +## FORBIDDEN PATTERNS: +- `// Handle XYZ formatting directly` - NEVER add explanatory comments +- `if let Some(node::Node::SomeType(inner)) = &node.node { /* manual implementation */ }` - NEVER manually implement child nodes +- Always use: `child_node.to_tokens(e)` instead of manual implementations + +## FILE PATHS: +- Nodes to implement: {} +- Target file: {} +- Test directory: {} + +## AST NODES TO IMPLEMENT: +{} + +## TOOLS AVAILABLE: +- Read: Read any file +- Write: Create new files +- Edit/MultiEdit: Modify existing files +- Bash: Run commands like cargo check, cargo test +- Grep: Search for patterns in files + +## VALIDATION PROCESS: +After creating a SQL file, validate it with: +```bash +cargo test -p pgt_pretty_print --test tests validate_test_data__[filename_without_sql] +``` +Example: For insert_stmt_0_80.sql, run: +```bash +cargo test -p pgt_pretty_print --test tests validate_test_data__insert_stmt_0_80 +``` +This ensures the SQL parses correctly and produces the expected AST node type. + +## OUTPUT VERIFICATION PROCESS: +After running formatter tests, check the quality by: +1. **Read the snapshot file**: `crates/pgt_pretty_print/tests/snapshots/tests__test_formatter__[filename].snap` +2. **Verify formatted output**: Check that the generated SQL looks properly formatted +3. **Validate round-trip**: Parse the formatted SQL and compare AST structure with original +4. **Look for common issues**: Missing whitespace, incorrect operator precedence, malformed syntax + +Example: For insert_stmt_0_80.sql, check: +`crates/pgt_pretty_print/tests/snapshots/tests__test_formatter__insert_stmt_0_80.snap` + +## RESPONSE FORMAT: +After each cycle, respond with just: +- "CONTINUING" to keep going +- "ALL_NODES_COMPLETED" when finished + +## CORRECT IMPLEMENTATION PATTERN: +```rust +impl ToTokens for SomeNode { + fn to_tokens(&self, e: &mut Elements) { + // Implementation for SomeNode + if let Some(ref child) = self.some_child { + child.to_tokens(e); // ✅ CORRECT - delegate to existing ToTokens + } + e.token(TokenKind::KEYWORD("SELECT".to_string())); + // NO COMMENTS ANYWHERE ELSE + } +} +``` + +## WRONG IMPLEMENTATION PATTERN (FORBIDDEN): +```rust +impl ToTokens for SomeNode { + fn to_tokens(&self, e: &mut Elements) { + // Implementation for SomeNode + if let Some(ref child) = self.some_child { + // Handle child formatting directly ❌ FORBIDDEN COMMENT + if let Some(node::Node::ChildType(inner)) = &child.node { + // ❌ FORBIDDEN - manual implementation of child + e.token(TokenKind::IDENT(inner.name.clone())); + } + } + } +} +``` + +## ACTION PLAN: +Start by reading nodes.rs to see current state, then begin the implementation loop. Work systematically through the nodes list, implementing ToTokens for each one with proper error handling and testing. + +ALWAYS delegate to existing ToTokens implementations. NEVER add explanatory comments. + +Continue this loop indefinitely until all nodes are implemented and all tests pass. +"#, + "xtask/agentic/ast_nodes.txt", + nodes_file_path.display(), + test_data_dir.display(), + ast_nodes_list + ); + + Ok(prompt) + } + + fn load_ast_nodes_list(&self) -> Result { + let ast_nodes_path = project_root().join("xtask/agentic/ast_nodes.txt"); + fs::read_to_string(ast_nodes_path) + .map_err(|e| anyhow::anyhow!("Failed to read AST nodes: {}", e)) + } +} + +pub fn run_autonomous_pretty_print_generator() -> Result<()> { + let mut generator = AutonomousPrettyPrintGenerator::new()?; + generator.run() +} diff --git a/xtask/agentic/src/lib.rs b/xtask/agentic/src/lib.rs index e2277205..014d1870 100644 --- a/xtask/agentic/src/lib.rs +++ b/xtask/agentic/src/lib.rs @@ -1,3 +1,4 @@ +pub mod autonomous_pretty_print; pub mod claude_session; pub mod pretty_print; @@ -10,10 +11,16 @@ pub enum AgenticCommand { /// Generate ToTokens implementations for pretty printing using AI #[bpaf(command("pretty-print-impls"))] PrettyPrintImpls, + /// Run autonomous pretty print implementation generator + #[bpaf(command("autonomous-pretty-print"))] + AutonomousPrettyPrint, } pub fn run_agentic_task(cmd: AgenticCommand) -> Result<()> { match cmd { AgenticCommand::PrettyPrintImpls => pretty_print::run_pretty_print_generator(), + AgenticCommand::AutonomousPrettyPrint => { + autonomous_pretty_print::run_autonomous_pretty_print_generator() + } } } diff --git a/xtask/agentic/src/main.rs b/xtask/agentic/src/main.rs index b33c03b4..4cb28189 100644 --- a/xtask/agentic/src/main.rs +++ b/xtask/agentic/src/main.rs @@ -2,7 +2,13 @@ use anyhow::Result; use xtask_agentic::{run_agentic_task, AgenticCommand}; fn main() -> Result<()> { - // For now, just run the pretty print implementation - let cmd = AgenticCommand::PrettyPrintImpls; + let args: Vec = std::env::args().collect(); + + let cmd = if args.len() > 1 && args[1] == "autonomous" { + AgenticCommand::AutonomousPrettyPrint + } else { + AgenticCommand::PrettyPrintImpls + }; + run_agentic_task(cmd) } diff --git a/xtask/agentic/src/pretty_print.rs b/xtask/agentic/src/pretty_print.rs index ee24d959..d4400186 100644 --- a/xtask/agentic/src/pretty_print.rs +++ b/xtask/agentic/src/pretty_print.rs @@ -528,16 +528,16 @@ impl PrettyPrintGenerator { filename: &str, ) -> Result { // Step 1: Test the specific file we created - println!("Step 1: Running file-specific validation..."); + self.log("Step 1: Running file-specific validation..."); if !self.validate_single_file(filename)? { return Ok(false); } // Step 2: Ask Claude to analyze if the SQL would create the expected node - println!( + self.log(&format!( "Step 2: Getting Claude's analysis of SQL for {} node...", expected_node - ); + )); let prompt = format!( "I generated this SQL to create a {} AST node:\n{}\n\n\ Does this SQL actually create a {} node when parsed by PostgreSQL? \ @@ -550,11 +550,11 @@ impl PrettyPrintGenerator { let is_correct = response.trim().starts_with("YES"); if !is_correct { - println!("Claude analysis: {}", response); + self.log(&format!("Claude analysis: {}", response)); return Ok(false); } - println!("✓ All validation steps passed!"); + self.log("✓ All validation steps passed!"); Ok(true) } @@ -583,11 +583,11 @@ impl PrettyPrintGenerator { if !success { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); - println!("Validation failed for {}:", filename); - println!("STDOUT:\n{}", stdout); - println!("STDERR:\n{}", stderr); + self.log(&format!("Validation failed for {}:", filename)); + self.log(&format!("STDOUT:\n{}", stdout)); + self.log(&format!("STDERR:\n{}", stderr)); } else { - println!("✓ Validation passed for {}", filename); + self.log(&format!("✓ Validation passed for {}", filename)); } Ok(success) @@ -663,9 +663,11 @@ impl PrettyPrintGenerator { IMPLEMENTATION INSTRUCTIONS:\n\ - Use the EXACT trait signature shown above\n\ - {}\n\ - - DO NOT add any comments in the code\n\ + - NEVER add comments to the Rust code - no // comments, no /* */ comments\n\ - Use existing TokenKind variants like INSERT_KW, INTO_KW, VALUES_KW, L_PAREN, R_PAREN, COMMA, etc.\n\ - - Add separator comment: // Implementation for {}\n\n\ + - Add ONLY this file separator comment BEFORE the impl: // Implementation for {}\n\ + - The impl block and all code inside must have ZERO comments - absolutely no explanatory comments\n\ + - CRITICAL: Do not add comments like '// Handle the VALUES clause' or any explanations\n\n\ After updating the file, just respond 'Done'.", node, iteration_guidance, @@ -698,8 +700,8 @@ impl PrettyPrintGenerator { Ok(()) } - fn check_compilation_with_errors(&self) -> Result<(bool, String)> { - println!("Running cargo check on pgt_pretty_print..."); + fn check_compilation_with_errors(&mut self) -> Result<(bool, String)> { + self.log("Running cargo check on pgt_pretty_print..."); let output = Command::new("cargo") .arg("check") @@ -714,10 +716,10 @@ impl PrettyPrintGenerator { let full_output = format!("STDOUT:\\n{}\\nSTDERR:\\n{}", stdout, stderr); if !success { - println!("Compilation failed:"); - println!("{}", full_output); + self.log("Compilation failed:"); + self.log(&full_output); } else { - println!("✓ Compilation successful"); + self.log("✓ Compilation successful"); } Ok((success, full_output)) @@ -739,7 +741,8 @@ impl PrettyPrintGenerator { Fix requirements:\\n\ - Keep the same overall structure and logic\\n\ - Fix syntax errors, type errors, missing imports, etc.\\n\ - - Preserve the rest of the file exactly\\n\\n\ + - Preserve the rest of the file exactly\\n\ + - NEVER add comments to the Rust code - no // comments, no /* */ comments\\n\\n\ After fixing, just respond 'Fixed'.", node, nodes_file.display(), @@ -751,11 +754,11 @@ impl PrettyPrintGenerator { Ok(()) } - fn run_formatter_tests(&self, _node: &str, filename: &str) -> Result<(bool, String)> { + fn run_formatter_tests(&mut self, _node: &str, filename: &str) -> Result<(bool, String)> { // Run the specific formatter test for the node we just implemented let test_name = format!("test_formatter__{}", filename.replace(".sql", "")); - println!("Running test: {}", test_name); + self.log(&format!("Running test: {}", test_name)); let output = Command::new("cargo") .arg("test") @@ -940,7 +943,9 @@ impl PrettyPrintGenerator { 3. Use Edit to improve the ToTokens implementation to handle the missing fields\\n\ 4. Focus on fields that appear in 'left' but are empty/missing in 'right'\\n\\n\ For example, if left has `values_lists: [...]` but right has `values_lists: []`, \ - then you need to add code to handle the values_lists field in your ToTokens implementation.\\n\\n\ + then you need to add code to handle the values_lists field in your ToTokens implementation.\\n\ + \\n\ + CRITICAL: NEVER add comments to the Rust code - no // comments, no /* */ comments\\n\\n\ After fixing, just respond 'Fixed'.", node, nodes_file.display(), test_errors, analysis, node ); diff --git a/xtask/src/agentic.rs b/xtask/src/agentic.rs index cf211b4f..49264855 100644 --- a/xtask/src/agentic.rs +++ b/xtask/src/agentic.rs @@ -9,6 +9,10 @@ pub fn run(cmd: Agentic, sh: &Shell) -> Result<()> { println!("Running agentic pretty print implementation generator..."); cmd!(sh, "cargo run -p xtask_agentic").run()?; } + AgenticCmd::AutonomousPrettyPrint(_) => { + println!("Running autonomous pretty print implementation generator..."); + cmd!(sh, "cargo run -p xtask_agentic -- autonomous").run()?; + } } Ok(()) } diff --git a/xtask/src/flags.rs b/xtask/src/flags.rs index 973334cc..506aa6af 100644 --- a/xtask/src/flags.rs +++ b/xtask/src/flags.rs @@ -23,6 +23,8 @@ xflags::xflags! { cmd agentic { /// Generate ToTokens implementations for pretty printing cmd pretty-print-impls {} + /// Run autonomous pretty print implementation generator + cmd autonomous-pretty-print {} } } } @@ -56,11 +58,15 @@ pub struct Agentic { #[derive(Debug)] pub enum AgenticCmd { PrettyPrintImpls(PrettyPrintImpls), + AutonomousPrettyPrint(AutonomousPrettyPrint), } #[derive(Debug)] pub struct PrettyPrintImpls; +#[derive(Debug)] +pub struct AutonomousPrettyPrint; + impl Xtask { #[allow(dead_code)] pub fn from_env_or_exit() -> Self { From 9a5fb500cced5dafcb724f47ea7b85461a4b3902 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Thu, 28 Aug 2025 23:09:24 +0200 Subject: [PATCH 13/15] progress --- xtask/agentic/src/autonomous_pretty_print.rs | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/xtask/agentic/src/autonomous_pretty_print.rs b/xtask/agentic/src/autonomous_pretty_print.rs index 2f4a5b26..129bff42 100644 --- a/xtask/agentic/src/autonomous_pretty_print.rs +++ b/xtask/agentic/src/autonomous_pretty_print.rs @@ -124,7 +124,7 @@ Your goal is to continuously implement ToTokens for all unimplemented nodes unti ## FORBIDDEN PATTERNS: - `// Handle XYZ formatting directly` - NEVER add explanatory comments -- `if let Some(node::Node::SomeType(inner)) = &node.node { /* manual implementation */ }` - NEVER manually implement child nodes +- `if let Some(node::Node::SomeType(inner)) = &node.node {{{{ /* manual implementation */ }}}}` - NEVER manually implement child nodes - Always use: `child_node.to_tokens(e)` instead of manual implementations ## FILE PATHS: @@ -170,32 +170,32 @@ After each cycle, respond with just: ## CORRECT IMPLEMENTATION PATTERN: ```rust -impl ToTokens for SomeNode { - fn to_tokens(&self, e: &mut Elements) { +impl ToTokens for SomeNode {{ + fn to_tokens(&self, e: &mut Elements) {{ // Implementation for SomeNode - if let Some(ref child) = self.some_child { + if let Some(ref child) = self.some_child {{ child.to_tokens(e); // ✅ CORRECT - delegate to existing ToTokens - } + }} e.token(TokenKind::KEYWORD("SELECT".to_string())); // NO COMMENTS ANYWHERE ELSE - } -} + }} +}} ``` ## WRONG IMPLEMENTATION PATTERN (FORBIDDEN): ```rust -impl ToTokens for SomeNode { - fn to_tokens(&self, e: &mut Elements) { +impl ToTokens for SomeNode {{ + fn to_tokens(&self, e: &mut Elements) {{ // Implementation for SomeNode - if let Some(ref child) = self.some_child { + if let Some(ref child) = self.some_child {{ // Handle child formatting directly ❌ FORBIDDEN COMMENT - if let Some(node::Node::ChildType(inner)) = &child.node { + if let Some(node::Node::ChildType(inner)) = &child.node {{ // ❌ FORBIDDEN - manual implementation of child e.token(TokenKind::IDENT(inner.name.clone())); - } - } - } -} + }} + }} + }} +}} ``` ## ACTION PLAN: From 215625af20c800ec85865b9a7d11e6e6aef705a5 Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 29 Aug 2025 10:10:14 +0200 Subject: [PATCH 14/15] progress --- .../src/codegen/group_kind.rs | 1 + crates/pgt_pretty_print/src/codegen/mod.rs | 1 + crates/pgt_pretty_print/src/emitter.rs | 61 +- crates/pgt_pretty_print/src/nodes.rs | 548 ++- ...tty_print__nodes__test__simple_select.snap | 8 + .../tests/data/alter_table_stmt_simple.sql | 1 + .../tests/data/create_stmt_simple.sql | 1 + .../tests/data/delete_stmt_simple.sql | 1 + .../tests/data/drop_stmt_simple.sql | 1 + .../tests/data/insert_stmt_0_80.sql | 1 + .../tests/data/insert_stmt_simple.sql | 1 + .../tests/data/truncate_stmt_simple.sql | 1 + .../tests/data/update_stmt_simple.sql | 1 + .../tests/data/view_stmt_simple.sql | 1 + .../tests__alter_table_stmt_simple.snap | 6 + .../snapshots/tests__create_stmt_simple.snap | 6 + .../snapshots/tests__delete_stmt_simple.snap | 6 + .../snapshots/tests__drop_stmt_simple.snap | 6 + .../snapshots/tests__insert_stmt_0_80.snap | 6 + .../snapshots/tests__insert_stmt_simple.snap | 6 + .../tests__truncate_stmt_simple.snap | 6 + .../snapshots/tests__update_stmt_simple.snap | 6 + .../snapshots/tests__view_stmt_simple.snap | 6 + crates/pgt_pretty_print_codegen/build.rs | 47 +- .../postgres/17-6.1.0/pg_query.proto | 4110 +++++++++++++++++ .../src/group_kind.rs | 24 + crates/pgt_pretty_print_codegen/src/lib.rs | 15 + .../src/proto_analyser.rs | 52 + .../src/token_kind.rs | 6 + xtask/agentic/src/autonomous_pretty_print.rs | 62 +- 30 files changed, 4929 insertions(+), 69 deletions(-) create mode 100644 crates/pgt_pretty_print/src/codegen/group_kind.rs create mode 100644 crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/create_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/insert_stmt_0_80.sql create mode 100644 crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/update_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/data/view_stmt_simple.sql create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_80.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_simple.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_simple.snap create mode 100644 crates/pgt_pretty_print_codegen/postgres/17-6.1.0/pg_query.proto create mode 100644 crates/pgt_pretty_print_codegen/src/group_kind.rs create mode 100644 crates/pgt_pretty_print_codegen/src/proto_analyser.rs diff --git a/crates/pgt_pretty_print/src/codegen/group_kind.rs b/crates/pgt_pretty_print/src/codegen/group_kind.rs new file mode 100644 index 00000000..859fc4cb --- /dev/null +++ b/crates/pgt_pretty_print/src/codegen/group_kind.rs @@ -0,0 +1 @@ +pgt_pretty_print_codegen::group_kind_codegen!(); diff --git a/crates/pgt_pretty_print/src/codegen/mod.rs b/crates/pgt_pretty_print/src/codegen/mod.rs index 92f817a0..3746b4ac 100644 --- a/crates/pgt_pretty_print/src/codegen/mod.rs +++ b/crates/pgt_pretty_print/src/codegen/mod.rs @@ -1 +1,2 @@ +pub mod group_kind; pub mod token_kind; diff --git a/crates/pgt_pretty_print/src/emitter.rs b/crates/pgt_pretty_print/src/emitter.rs index f9833872..8216e128 100644 --- a/crates/pgt_pretty_print/src/emitter.rs +++ b/crates/pgt_pretty_print/src/emitter.rs @@ -1,3 +1,4 @@ +pub use crate::codegen::group_kind::GroupKind; pub use crate::codegen::token_kind::TokenKind; #[derive(Debug, Clone, PartialEq)] @@ -18,6 +19,7 @@ pub enum LayoutEvent { GroupStart { id: Option, break_parent: bool, + kind: GroupKind, }, GroupEnd, IndentStart, @@ -45,9 +47,62 @@ impl EventEmitter { self.events.push(LayoutEvent::Line(line_type)); } - pub fn group_start(&mut self, id: Option, break_parent: bool) { - self.events - .push(LayoutEvent::GroupStart { id, break_parent }); + pub fn group_start(&mut self, kind: GroupKind, id: Option, break_parent: bool) { + self.events.push(LayoutEvent::GroupStart { + id, + break_parent, + kind, + }); + } + + pub fn is_within_group(&self, target_kind: GroupKind) -> bool { + let mut depth = 0; + for event in self.events.iter().rev() { + match event { + LayoutEvent::GroupEnd => depth += 1, + LayoutEvent::GroupStart { kind, .. } => { + if depth == 0 && *kind == target_kind { + return true; + } + if depth > 0 { + depth -= 1; + } + } + _ => {} + } + } + false + } + + pub fn parent_group(&self) -> Option { + let mut depth = 0; + for event in self.events.iter().rev() { + match event { + LayoutEvent::GroupEnd => depth += 1, + LayoutEvent::GroupStart { kind, .. } => { + if depth == 1 { + return Some(kind.clone()); + } + if depth > 0 { + depth -= 1; + } + } + _ => {} + } + } + None + } + + pub fn is_top_level(&self) -> bool { + let mut depth = 0; + for event in &self.events { + match event { + LayoutEvent::GroupStart { .. } => depth += 1, + LayoutEvent::GroupEnd => depth -= 1, + _ => {} + } + } + depth == 1 } pub fn group_end(&mut self) { diff --git a/crates/pgt_pretty_print/src/nodes.rs b/crates/pgt_pretty_print/src/nodes.rs index f837ec71..d2b1b508 100644 --- a/crates/pgt_pretty_print/src/nodes.rs +++ b/crates/pgt_pretty_print/src/nodes.rs @@ -1,17 +1,9 @@ use crate::{ TokenKind, - emitter::{EventEmitter, LineType, ToTokens}, + emitter::{EventEmitter, GroupKind, LineType, ToTokens}, }; -impl ToTokens for pgt_query::Node { - fn to_tokens(&self, e: &mut EventEmitter) { - if let Some(node) = &self.node { - node.to_tokens(e); - } - } -} - -impl ToTokens for pgt_query::protobuf::node::Node { +impl ToTokens for pgt_query::NodeEnum { fn to_tokens(&self, e: &mut EventEmitter) { match self { pgt_query::protobuf::node::Node::SelectStmt(stmt) => stmt.as_ref().to_tokens(e), @@ -20,6 +12,20 @@ impl ToTokens for pgt_query::protobuf::node::Node { pgt_query::protobuf::node::Node::String(string) => string.to_tokens(e), pgt_query::protobuf::node::Node::RangeVar(string) => string.to_tokens(e), pgt_query::protobuf::node::Node::FuncCall(func_call) => func_call.to_tokens(e), + pgt_query::protobuf::node::Node::InsertStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::List(list) => list.to_tokens(e), + pgt_query::protobuf::node::Node::AConst(const_val) => const_val.to_tokens(e), + pgt_query::protobuf::node::Node::DeleteStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AExpr(expr) => expr.to_tokens(e), + pgt_query::protobuf::node::Node::UpdateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::CreateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::ColumnDef(def) => def.to_tokens(e), + pgt_query::protobuf::node::Node::TypeName(type_name) => type_name.to_tokens(e), + pgt_query::protobuf::node::Node::DropStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::TruncateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AlterTableStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AlterTableCmd(cmd) => cmd.to_tokens(e), + pgt_query::protobuf::node::Node::ViewStmt(stmt) => stmt.to_tokens(e), _ => { unimplemented!("Node type {:?} not implemented for to_tokens", self); } @@ -27,43 +33,89 @@ impl ToTokens for pgt_query::protobuf::node::Node { } } -impl ToTokens for pgt_query::protobuf::SelectStmt { +impl ToTokens for pgt_query::Node { fn to_tokens(&self, e: &mut EventEmitter) { - e.group_start(None, false); - - e.token(TokenKind::SELECT_KW); + if let Some(node) = &self.node { + match node { + pgt_query::protobuf::node::Node::SelectStmt(stmt) => stmt.as_ref().to_tokens(e), + pgt_query::protobuf::node::Node::ResTarget(target) => target.to_tokens(e), + pgt_query::protobuf::node::Node::ColumnRef(col_ref) => col_ref.to_tokens(e), + pgt_query::protobuf::node::Node::String(string) => string.to_tokens(e), + pgt_query::protobuf::node::Node::RangeVar(string) => string.to_tokens(e), + pgt_query::protobuf::node::Node::FuncCall(func_call) => func_call.to_tokens(e), + pgt_query::protobuf::node::Node::InsertStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::List(list) => list.to_tokens(e), + pgt_query::protobuf::node::Node::AConst(const_val) => const_val.to_tokens(e), + pgt_query::protobuf::node::Node::DeleteStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AExpr(expr) => expr.to_tokens(e), + pgt_query::protobuf::node::Node::UpdateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::CreateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::ColumnDef(def) => def.to_tokens(e), + pgt_query::protobuf::node::Node::TypeName(type_name) => type_name.to_tokens(e), + pgt_query::protobuf::node::Node::DropStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::TruncateStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AlterTableStmt(stmt) => stmt.to_tokens(e), + pgt_query::protobuf::node::Node::AlterTableCmd(cmd) => cmd.to_tokens(e), + pgt_query::protobuf::node::Node::ViewStmt(stmt) => stmt.to_tokens(e), + _ => { + unimplemented!("Node type {:?} not implemented for to_tokens", node); + } + } + } + } +} - if !self.target_list.is_empty() { - e.indent_start(); - e.line(LineType::SoftOrSpace); +impl ToTokens for pgt_query::protobuf::SelectStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::SelectStmt, None, false); - for (i, target) in self.target_list.iter().enumerate() { + if !self.values_lists.is_empty() { + e.token(TokenKind::VALUES_KW); + e.space(); + for (i, values_list) in self.values_lists.iter().enumerate() { if i > 0 { e.token(TokenKind::COMMA); - e.line(LineType::SoftOrSpace); + e.space(); } - target.to_tokens(e); + values_list.to_tokens(e); } - e.indent_end(); - } + } else { + e.token(TokenKind::SELECT_KW); - if !self.from_clause.is_empty() { - e.line(LineType::SoftOrSpace); - e.token(TokenKind::FROM_KW); - e.line(LineType::SoftOrSpace); + if !self.target_list.is_empty() { + e.indent_start(); + e.line(LineType::SoftOrSpace); - e.indent_start(); - for (i, from) in self.from_clause.iter().enumerate() { - if i > 0 { - e.token(TokenKind::COMMA); - e.line(LineType::SoftOrSpace); + for (i, target) in self.target_list.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + target.to_tokens(e); } - from.to_tokens(e); + e.indent_end(); + } + + if !self.from_clause.is_empty() { + e.line(LineType::SoftOrSpace); + e.token(TokenKind::FROM_KW); + e.line(LineType::SoftOrSpace); + + e.indent_start(); + for (i, from) in self.from_clause.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.line(LineType::SoftOrSpace); + } + from.to_tokens(e); + } + e.indent_end(); } - e.indent_end(); } - e.token(TokenKind::SEMICOLON); + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } e.group_end(); } @@ -71,17 +123,30 @@ impl ToTokens for pgt_query::protobuf::SelectStmt { impl ToTokens for pgt_query::protobuf::ResTarget { fn to_tokens(&self, e: &mut EventEmitter) { - e.group_start(None, false); - - if let Some(ref val) = self.val { - val.to_tokens(e); - } - - if !self.name.is_empty() { - e.space(); - e.token(TokenKind::AS_KW); - e.space(); - e.token(TokenKind::IDENT(self.name.clone())); + e.group_start(GroupKind::ResTarget, None, false); + + if e.is_within_group(GroupKind::UpdateStmt) { + if !self.name.is_empty() { + e.token(TokenKind::IDENT(self.name.clone())); + e.space(); + e.token(TokenKind::IDENT("=".to_string())); + e.space(); + } + if let Some(ref val) = self.val { + val.to_tokens(e); + } + } else { + if let Some(ref val) = self.val { + val.to_tokens(e); + if !self.name.is_empty() { + e.space(); + e.token(TokenKind::AS_KW); + e.space(); + e.token(TokenKind::IDENT(self.name.clone())); + } + } else if !self.name.is_empty() { + e.token(TokenKind::IDENT(self.name.clone())); + } } e.group_end(); @@ -90,7 +155,7 @@ impl ToTokens for pgt_query::protobuf::ResTarget { impl ToTokens for pgt_query::protobuf::ColumnRef { fn to_tokens(&self, e: &mut EventEmitter) { - e.group_start(None, false); + e.group_start(GroupKind::ColumnRef, None, false); for (i, field) in self.fields.iter().enumerate() { if i > 0 { @@ -111,7 +176,7 @@ impl ToTokens for pgt_query::protobuf::String { impl ToTokens for pgt_query::protobuf::RangeVar { fn to_tokens(&self, e: &mut EventEmitter) { - e.group_start(None, false); + e.group_start(GroupKind::RangeVar, None, false); if !self.schemaname.is_empty() { e.token(TokenKind::IDENT(self.schemaname.clone())); @@ -126,7 +191,7 @@ impl ToTokens for pgt_query::protobuf::RangeVar { impl ToTokens for pgt_query::protobuf::FuncCall { fn to_tokens(&self, e: &mut EventEmitter) { - e.group_start(None, false); + e.group_start(GroupKind::FuncCall, None, false); for (i, name) in self.funcname.iter().enumerate() { if i > 0 { @@ -139,7 +204,7 @@ impl ToTokens for pgt_query::protobuf::FuncCall { e.token(TokenKind::L_PAREN); if !self.args.is_empty() { - e.group_start(None, true); + e.group_start(GroupKind::FuncCall, None, true); e.line(LineType::SoftOrSpace); e.indent_start(); @@ -161,6 +226,391 @@ impl ToTokens for pgt_query::protobuf::FuncCall { } } +impl ToTokens for pgt_query::protobuf::InsertStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::InsertStmt, None, false); + + e.token(TokenKind::INSERT_KW); + e.space(); + e.token(TokenKind::INTO_KW); + e.space(); + + if let Some(ref relation) = self.relation { + relation.to_tokens(e); + } + + if !self.cols.is_empty() { + e.space(); + e.token(TokenKind::L_PAREN); + for (i, col) in self.cols.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + col.to_tokens(e); + } + e.token(TokenKind::R_PAREN); + } + + if let Some(ref select_stmt) = self.select_stmt { + e.space(); + select_stmt.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::List { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::List, None, false); + + if e.is_within_group(GroupKind::DropStmt) { + for (i, item) in self.items.iter().enumerate() { + if i > 0 { + e.token(TokenKind::DOT); + } + item.to_tokens(e); + } + } else { + e.token(TokenKind::L_PAREN); + for (i, item) in self.items.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + item.to_tokens(e); + } + e.token(TokenKind::R_PAREN); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::AConst { + fn to_tokens(&self, e: &mut EventEmitter) { + if let Some(ref val) = self.val { + match val { + pgt_query::protobuf::a_const::Val::Ival(ival) => { + e.token(TokenKind::IDENT(ival.ival.to_string())); + } + pgt_query::protobuf::a_const::Val::Fval(fval) => { + e.token(TokenKind::IDENT(fval.fval.clone())); + } + pgt_query::protobuf::a_const::Val::Boolval(boolval) => { + let val_str = if boolval.boolval { "TRUE" } else { "FALSE" }; + e.token(TokenKind::IDENT(val_str.to_string())); + } + pgt_query::protobuf::a_const::Val::Sval(sval) => { + e.token(TokenKind::STRING(format!("'{}'", sval.sval))); + } + pgt_query::protobuf::a_const::Val::Bsval(bsval) => { + e.token(TokenKind::STRING(bsval.bsval.clone())); + } + } + } + } +} + +impl ToTokens for pgt_query::protobuf::DeleteStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::DeleteStmt, None, false); + + e.token(TokenKind::DELETE_KW); + e.space(); + e.token(TokenKind::FROM_KW); + e.space(); + + if let Some(ref relation) = self.relation { + relation.to_tokens(e); + } + + if let Some(ref where_clause) = self.where_clause { + e.space(); + e.token(TokenKind::WHERE_KW); + e.space(); + where_clause.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::AExpr { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::AExpr, None, false); + + if let Some(ref lexpr) = self.lexpr { + lexpr.to_tokens(e); + } + + if !self.name.is_empty() { + e.space(); + for name in &self.name { + name.to_tokens(e); + } + e.space(); + } + + if let Some(ref rexpr) = self.rexpr { + rexpr.to_tokens(e); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::UpdateStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::UpdateStmt, None, false); + + e.token(TokenKind::UPDATE_KW); + e.space(); + + if let Some(ref relation) = self.relation { + relation.to_tokens(e); + } + + if !self.target_list.is_empty() { + e.space(); + e.token(TokenKind::SET_KW); + e.space(); + for (i, target) in self.target_list.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + target.to_tokens(e); + } + } + + if let Some(ref where_clause) = self.where_clause { + e.space(); + e.token(TokenKind::WHERE_KW); + e.space(); + where_clause.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::CreateStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::CreateStmt, None, false); + + e.token(TokenKind::CREATE_KW); + e.space(); + e.token(TokenKind::TABLE_KW); + e.space(); + + if let Some(ref relation) = self.relation { + relation.to_tokens(e); + } + + if !self.table_elts.is_empty() { + e.space(); + e.token(TokenKind::L_PAREN); + for (i, elt) in self.table_elts.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + elt.to_tokens(e); + } + e.token(TokenKind::R_PAREN); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::ColumnDef { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::ColumnDef, None, false); + + e.token(TokenKind::IDENT(self.colname.clone())); + + if let Some(ref type_name) = self.type_name { + e.space(); + type_name.to_tokens(e); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::TypeName { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::TypeName, None, false); + + for (i, name) in self.names.iter().enumerate() { + if i > 0 { + e.token(TokenKind::DOT); + } + name.to_tokens(e); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::DropStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::DropStmt, None, false); + + e.token(TokenKind::DROP_KW); + e.space(); + e.token(TokenKind::TABLE_KW); + e.space(); + + for (i, obj) in self.objects.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + obj.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::TruncateStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::TruncateStmt, None, false); + + e.token(TokenKind::TRUNCATE_KW); + e.space(); + e.token(TokenKind::TABLE_KW); + e.space(); + + for (i, rel) in self.relations.iter().enumerate() { + if i > 0 { + e.token(TokenKind::COMMA); + e.space(); + } + rel.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::AlterTableStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::AlterTableStmt, None, false); + + e.token(TokenKind::ALTER_KW); + e.space(); + e.token(TokenKind::TABLE_KW); + e.space(); + + if let Some(ref relation) = self.relation { + relation.to_tokens(e); + } + + for (i, cmd) in self.cmds.iter().enumerate() { + if i == 0 { + e.space(); + } else { + e.token(TokenKind::COMMA); + e.space(); + } + cmd.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::AlterTableCmd { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::AlterTableCmd, None, false); + + use pgt_query::protobuf::AlterTableType; + + match AlterTableType::try_from(self.subtype).unwrap() { + AlterTableType::AtAddColumn => { + e.token(TokenKind::ADD_KW); + e.space(); + e.token(TokenKind::COLUMN_KW); + if let Some(ref def) = self.def { + e.space(); + def.to_tokens(e); + } + } + AlterTableType::AtDropColumn => { + e.token(TokenKind::DROP_KW); + e.space(); + e.token(TokenKind::COLUMN_KW); + e.space(); + e.token(TokenKind::IDENT(self.name.clone())); + } + _ => todo!(), + } + + e.group_end(); + } +} + +impl ToTokens for pgt_query::protobuf::ViewStmt { + fn to_tokens(&self, e: &mut EventEmitter) { + e.group_start(GroupKind::ViewStmt, None, false); + + e.token(TokenKind::CREATE_KW); + e.space(); + e.token(TokenKind::VIEW_KW); + e.space(); + + if let Some(ref view) = self.view { + view.to_tokens(e); + } + + if let Some(ref query) = self.query { + e.space(); + e.token(TokenKind::AS_KW); + e.space(); + query.to_tokens(e); + } + + if e.is_top_level() { + e.token(TokenKind::SEMICOLON); + } + + e.group_end(); + } +} + #[cfg(test)] mod test { use crate::emitter::{EventEmitter, ToTokens}; diff --git a/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap b/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap index 6e71658a..a0b24a7b 100644 --- a/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap +++ b/crates/pgt_pretty_print/src/snapshots/pgt_pretty_print__nodes__test__simple_select.snap @@ -7,6 +7,7 @@ snapshot_kind: text GroupStart { id: None, break_parent: false, + kind: SelectStmt, }, Token( SELECT_KW, @@ -18,10 +19,12 @@ snapshot_kind: text GroupStart { id: None, break_parent: false, + kind: ResTarget, }, GroupStart { id: None, break_parent: false, + kind: ColumnRef, }, Token( IDENT( @@ -65,10 +68,12 @@ snapshot_kind: text GroupStart { id: None, break_parent: false, + kind: ResTarget, }, GroupStart { id: None, break_parent: false, + kind: ColumnRef, }, Token( IDENT( @@ -96,10 +101,12 @@ snapshot_kind: text GroupStart { id: None, break_parent: false, + kind: ResTarget, }, GroupStart { id: None, break_parent: false, + kind: ColumnRef, }, Token( IDENT( @@ -122,6 +129,7 @@ snapshot_kind: text GroupStart { id: None, break_parent: false, + kind: RangeVar, }, Token( IDENT( diff --git a/crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql new file mode 100644 index 00000000..7b008ffd --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN email TEXT; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/create_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/create_stmt_simple.sql new file mode 100644 index 00000000..4bc9d77d --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/create_stmt_simple.sql @@ -0,0 +1 @@ +CREATE TABLE users (id TEXT, name TEXT); \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql new file mode 100644 index 00000000..cad242da --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql @@ -0,0 +1 @@ +DELETE FROM users WHERE id = 1; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql new file mode 100644 index 00000000..441087ad --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql @@ -0,0 +1 @@ +DROP TABLE users; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/insert_stmt_0_80.sql b/crates/pgt_pretty_print/tests/data/insert_stmt_0_80.sql new file mode 100644 index 00000000..56840417 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/insert_stmt_0_80.sql @@ -0,0 +1 @@ +INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com'); \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql new file mode 100644 index 00000000..2c9fdb7e --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql @@ -0,0 +1 @@ +INSERT INTO users VALUES (1, 'John'); \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql new file mode 100644 index 00000000..75e9657c --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql @@ -0,0 +1 @@ +TRUNCATE TABLE users; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/update_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/update_stmt_simple.sql new file mode 100644 index 00000000..6e6e1115 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/update_stmt_simple.sql @@ -0,0 +1 @@ +UPDATE users SET name = 'Jane Doe' WHERE id = 1; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/view_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/view_stmt_simple.sql new file mode 100644 index 00000000..1d9b6815 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/view_stmt_simple.sql @@ -0,0 +1 @@ +CREATE VIEW user_view AS SELECT id, name FROM users; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_simple.snap new file mode 100644 index 00000000..545e285a --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql +snapshot_kind: text +--- +ALTER TABLE users ADD COLUMN email text; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_simple.snap new file mode 100644 index 00000000..c2345300 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/create_stmt_simple.sql +snapshot_kind: text +--- +CREATE TABLE users (id text, name text); diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_simple.snap new file mode 100644 index 00000000..42dedca2 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql +snapshot_kind: text +--- +DELETE FROM users WHERE id = 1; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_simple.snap new file mode 100644 index 00000000..c9fdfd85 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql +snapshot_kind: text +--- +DROP TABLE users; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_80.snap b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_80.snap new file mode 100644 index 00000000..f11069dc --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_80.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/insert_stmt_0_80.sql +snapshot_kind: text +--- +INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com'); diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_simple.snap new file mode 100644 index 00000000..50336e54 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql +snapshot_kind: text +--- +INSERT INTO users VALUES (1, 'John'); diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_simple.snap new file mode 100644 index 00000000..0678987d --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql +snapshot_kind: text +--- +TRUNCATE TABLE users; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_simple.snap new file mode 100644 index 00000000..1f34cc1c --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/update_stmt_simple.sql +snapshot_kind: text +--- +UPDATE users SET name = 'Jane Doe' WHERE id = 1; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_simple.snap b/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_simple.snap new file mode 100644 index 00000000..9350f4b8 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_simple.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/view_stmt_simple.sql +snapshot_kind: text +--- +CREATE VIEW user_view AS SELECT id, name FROM users; diff --git a/crates/pgt_pretty_print_codegen/build.rs b/crates/pgt_pretty_print_codegen/build.rs index 70c9635d..c4bb1a74 100644 --- a/crates/pgt_pretty_print_codegen/build.rs +++ b/crates/pgt_pretty_print_codegen/build.rs @@ -10,26 +10,27 @@ static LIBPG_QUERY_TAG: &str = "17-6.1.0"; fn main() -> Result<(), Box> { let version = LIBPG_QUERY_TAG.to_string(); - // Check for the postgres header file in the source tree first let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); - let headers_dir = manifest_dir.join("postgres").join(&version); - let kwlist_path = headers_dir.join("kwlist.h"); + let postgres_dir = manifest_dir.join("postgres").join(&version); + let kwlist_path = postgres_dir.join("kwlist.h"); + let proto_path = postgres_dir.join("pg_query.proto"); + + if !postgres_dir.exists() { + fs::create_dir_all(&postgres_dir)?; + } - // Only download if the file doesn't exist if !kwlist_path.exists() { println!( "cargo:warning=Downloading kwlist.h for libpg_query {}", version ); - fs::create_dir_all(&headers_dir)?; - - let proto_url = format!( + let kwlist_url = format!( "https://raw.githubusercontent.com/pganalyze/libpg_query/{}/src/postgres/include/parser/kwlist.h", version ); - let response = ureq::get(&proto_url).call()?; + let response = ureq::get(&kwlist_url).call()?; let content = response.into_string()?; let mut file = fs::File::create(&kwlist_path)?; @@ -38,12 +39,42 @@ fn main() -> Result<(), Box> { println!("cargo:warning=Successfully downloaded kwlist.h"); } + if !proto_path.exists() { + println!( + "cargo:warning=Downloading pg_query.proto for libpg_query {}", + version + ); + + let proto_url = format!( + "https://raw.githubusercontent.com/pganalyze/libpg_query/{}/protobuf/pg_query.proto", + version + ); + + let response = ureq::get(&proto_url).call()?; + let proto_content = response.into_string()?; + + let mut file = fs::File::create(&proto_path)?; + file.write_all(proto_content.as_bytes())?; + + println!( + "cargo:warning=Successfully downloaded pg_query.proto to {}", + proto_path.display() + ); + } + println!( "cargo:rustc-env=PG_QUERY_KWLIST_PATH={}", kwlist_path.display() ); + println!( + "cargo:rustc-env=PG_QUERY_PROTO_PATH={}", + proto_path.display() + ); + println!("cargo:rerun-if-changed={}", kwlist_path.display()); + println!("cargo:rerun-if-changed={}", proto_path.display()); + Ok(()) } diff --git a/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/pg_query.proto b/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/pg_query.proto new file mode 100644 index 00000000..24a8f14c --- /dev/null +++ b/crates/pgt_pretty_print_codegen/postgres/17-6.1.0/pg_query.proto @@ -0,0 +1,4110 @@ +// This file is autogenerated by ./scripts/generate_protobuf_and_funcs.rb + +syntax = "proto3"; + +package pg_query; + +message ParseResult { + int32 version = 1; + repeated RawStmt stmts = 2; +} + +message ScanResult { + int32 version = 1; + repeated ScanToken tokens = 2; +} + +message Node { + oneof node { + Alias alias = 1 [json_name="Alias"]; + RangeVar range_var = 2 [json_name="RangeVar"]; + TableFunc table_func = 3 [json_name="TableFunc"]; + IntoClause into_clause = 4 [json_name="IntoClause"]; + Var var = 5 [json_name="Var"]; + Param param = 6 [json_name="Param"]; + Aggref aggref = 7 [json_name="Aggref"]; + GroupingFunc grouping_func = 8 [json_name="GroupingFunc"]; + WindowFunc window_func = 9 [json_name="WindowFunc"]; + WindowFuncRunCondition window_func_run_condition = 10 [json_name="WindowFuncRunCondition"]; + MergeSupportFunc merge_support_func = 11 [json_name="MergeSupportFunc"]; + SubscriptingRef subscripting_ref = 12 [json_name="SubscriptingRef"]; + FuncExpr func_expr = 13 [json_name="FuncExpr"]; + NamedArgExpr named_arg_expr = 14 [json_name="NamedArgExpr"]; + OpExpr op_expr = 15 [json_name="OpExpr"]; + DistinctExpr distinct_expr = 16 [json_name="DistinctExpr"]; + NullIfExpr null_if_expr = 17 [json_name="NullIfExpr"]; + ScalarArrayOpExpr scalar_array_op_expr = 18 [json_name="ScalarArrayOpExpr"]; + BoolExpr bool_expr = 19 [json_name="BoolExpr"]; + SubLink sub_link = 20 [json_name="SubLink"]; + SubPlan sub_plan = 21 [json_name="SubPlan"]; + AlternativeSubPlan alternative_sub_plan = 22 [json_name="AlternativeSubPlan"]; + FieldSelect field_select = 23 [json_name="FieldSelect"]; + FieldStore field_store = 24 [json_name="FieldStore"]; + RelabelType relabel_type = 25 [json_name="RelabelType"]; + CoerceViaIO coerce_via_io = 26 [json_name="CoerceViaIO"]; + ArrayCoerceExpr array_coerce_expr = 27 [json_name="ArrayCoerceExpr"]; + ConvertRowtypeExpr convert_rowtype_expr = 28 [json_name="ConvertRowtypeExpr"]; + CollateExpr collate_expr = 29 [json_name="CollateExpr"]; + CaseExpr case_expr = 30 [json_name="CaseExpr"]; + CaseWhen case_when = 31 [json_name="CaseWhen"]; + CaseTestExpr case_test_expr = 32 [json_name="CaseTestExpr"]; + ArrayExpr array_expr = 33 [json_name="ArrayExpr"]; + RowExpr row_expr = 34 [json_name="RowExpr"]; + RowCompareExpr row_compare_expr = 35 [json_name="RowCompareExpr"]; + CoalesceExpr coalesce_expr = 36 [json_name="CoalesceExpr"]; + MinMaxExpr min_max_expr = 37 [json_name="MinMaxExpr"]; + SQLValueFunction sqlvalue_function = 38 [json_name="SQLValueFunction"]; + XmlExpr xml_expr = 39 [json_name="XmlExpr"]; + JsonFormat json_format = 40 [json_name="JsonFormat"]; + JsonReturning json_returning = 41 [json_name="JsonReturning"]; + JsonValueExpr json_value_expr = 42 [json_name="JsonValueExpr"]; + JsonConstructorExpr json_constructor_expr = 43 [json_name="JsonConstructorExpr"]; + JsonIsPredicate json_is_predicate = 44 [json_name="JsonIsPredicate"]; + JsonBehavior json_behavior = 45 [json_name="JsonBehavior"]; + JsonExpr json_expr = 46 [json_name="JsonExpr"]; + JsonTablePath json_table_path = 47 [json_name="JsonTablePath"]; + JsonTablePathScan json_table_path_scan = 48 [json_name="JsonTablePathScan"]; + JsonTableSiblingJoin json_table_sibling_join = 49 [json_name="JsonTableSiblingJoin"]; + NullTest null_test = 50 [json_name="NullTest"]; + BooleanTest boolean_test = 51 [json_name="BooleanTest"]; + MergeAction merge_action = 52 [json_name="MergeAction"]; + CoerceToDomain coerce_to_domain = 53 [json_name="CoerceToDomain"]; + CoerceToDomainValue coerce_to_domain_value = 54 [json_name="CoerceToDomainValue"]; + SetToDefault set_to_default = 55 [json_name="SetToDefault"]; + CurrentOfExpr current_of_expr = 56 [json_name="CurrentOfExpr"]; + NextValueExpr next_value_expr = 57 [json_name="NextValueExpr"]; + InferenceElem inference_elem = 58 [json_name="InferenceElem"]; + TargetEntry target_entry = 59 [json_name="TargetEntry"]; + RangeTblRef range_tbl_ref = 60 [json_name="RangeTblRef"]; + JoinExpr join_expr = 61 [json_name="JoinExpr"]; + FromExpr from_expr = 62 [json_name="FromExpr"]; + OnConflictExpr on_conflict_expr = 63 [json_name="OnConflictExpr"]; + Query query = 64 [json_name="Query"]; + TypeName type_name = 65 [json_name="TypeName"]; + ColumnRef column_ref = 66 [json_name="ColumnRef"]; + ParamRef param_ref = 67 [json_name="ParamRef"]; + A_Expr a_expr = 68 [json_name="A_Expr"]; + TypeCast type_cast = 69 [json_name="TypeCast"]; + CollateClause collate_clause = 70 [json_name="CollateClause"]; + RoleSpec role_spec = 71 [json_name="RoleSpec"]; + FuncCall func_call = 72 [json_name="FuncCall"]; + A_Star a_star = 73 [json_name="A_Star"]; + A_Indices a_indices = 74 [json_name="A_Indices"]; + A_Indirection a_indirection = 75 [json_name="A_Indirection"]; + A_ArrayExpr a_array_expr = 76 [json_name="A_ArrayExpr"]; + ResTarget res_target = 77 [json_name="ResTarget"]; + MultiAssignRef multi_assign_ref = 78 [json_name="MultiAssignRef"]; + SortBy sort_by = 79 [json_name="SortBy"]; + WindowDef window_def = 80 [json_name="WindowDef"]; + RangeSubselect range_subselect = 81 [json_name="RangeSubselect"]; + RangeFunction range_function = 82 [json_name="RangeFunction"]; + RangeTableFunc range_table_func = 83 [json_name="RangeTableFunc"]; + RangeTableFuncCol range_table_func_col = 84 [json_name="RangeTableFuncCol"]; + RangeTableSample range_table_sample = 85 [json_name="RangeTableSample"]; + ColumnDef column_def = 86 [json_name="ColumnDef"]; + TableLikeClause table_like_clause = 87 [json_name="TableLikeClause"]; + IndexElem index_elem = 88 [json_name="IndexElem"]; + DefElem def_elem = 89 [json_name="DefElem"]; + LockingClause locking_clause = 90 [json_name="LockingClause"]; + XmlSerialize xml_serialize = 91 [json_name="XmlSerialize"]; + PartitionElem partition_elem = 92 [json_name="PartitionElem"]; + PartitionSpec partition_spec = 93 [json_name="PartitionSpec"]; + PartitionBoundSpec partition_bound_spec = 94 [json_name="PartitionBoundSpec"]; + PartitionRangeDatum partition_range_datum = 95 [json_name="PartitionRangeDatum"]; + SinglePartitionSpec single_partition_spec = 96 [json_name="SinglePartitionSpec"]; + PartitionCmd partition_cmd = 97 [json_name="PartitionCmd"]; + RangeTblEntry range_tbl_entry = 98 [json_name="RangeTblEntry"]; + RTEPermissionInfo rtepermission_info = 99 [json_name="RTEPermissionInfo"]; + RangeTblFunction range_tbl_function = 100 [json_name="RangeTblFunction"]; + TableSampleClause table_sample_clause = 101 [json_name="TableSampleClause"]; + WithCheckOption with_check_option = 102 [json_name="WithCheckOption"]; + SortGroupClause sort_group_clause = 103 [json_name="SortGroupClause"]; + GroupingSet grouping_set = 104 [json_name="GroupingSet"]; + WindowClause window_clause = 105 [json_name="WindowClause"]; + RowMarkClause row_mark_clause = 106 [json_name="RowMarkClause"]; + WithClause with_clause = 107 [json_name="WithClause"]; + InferClause infer_clause = 108 [json_name="InferClause"]; + OnConflictClause on_conflict_clause = 109 [json_name="OnConflictClause"]; + CTESearchClause ctesearch_clause = 110 [json_name="CTESearchClause"]; + CTECycleClause ctecycle_clause = 111 [json_name="CTECycleClause"]; + CommonTableExpr common_table_expr = 112 [json_name="CommonTableExpr"]; + MergeWhenClause merge_when_clause = 113 [json_name="MergeWhenClause"]; + TriggerTransition trigger_transition = 114 [json_name="TriggerTransition"]; + JsonOutput json_output = 115 [json_name="JsonOutput"]; + JsonArgument json_argument = 116 [json_name="JsonArgument"]; + JsonFuncExpr json_func_expr = 117 [json_name="JsonFuncExpr"]; + JsonTablePathSpec json_table_path_spec = 118 [json_name="JsonTablePathSpec"]; + JsonTable json_table = 119 [json_name="JsonTable"]; + JsonTableColumn json_table_column = 120 [json_name="JsonTableColumn"]; + JsonKeyValue json_key_value = 121 [json_name="JsonKeyValue"]; + JsonParseExpr json_parse_expr = 122 [json_name="JsonParseExpr"]; + JsonScalarExpr json_scalar_expr = 123 [json_name="JsonScalarExpr"]; + JsonSerializeExpr json_serialize_expr = 124 [json_name="JsonSerializeExpr"]; + JsonObjectConstructor json_object_constructor = 125 [json_name="JsonObjectConstructor"]; + JsonArrayConstructor json_array_constructor = 126 [json_name="JsonArrayConstructor"]; + JsonArrayQueryConstructor json_array_query_constructor = 127 [json_name="JsonArrayQueryConstructor"]; + JsonAggConstructor json_agg_constructor = 128 [json_name="JsonAggConstructor"]; + JsonObjectAgg json_object_agg = 129 [json_name="JsonObjectAgg"]; + JsonArrayAgg json_array_agg = 130 [json_name="JsonArrayAgg"]; + RawStmt raw_stmt = 131 [json_name="RawStmt"]; + InsertStmt insert_stmt = 132 [json_name="InsertStmt"]; + DeleteStmt delete_stmt = 133 [json_name="DeleteStmt"]; + UpdateStmt update_stmt = 134 [json_name="UpdateStmt"]; + MergeStmt merge_stmt = 135 [json_name="MergeStmt"]; + SelectStmt select_stmt = 136 [json_name="SelectStmt"]; + SetOperationStmt set_operation_stmt = 137 [json_name="SetOperationStmt"]; + ReturnStmt return_stmt = 138 [json_name="ReturnStmt"]; + PLAssignStmt plassign_stmt = 139 [json_name="PLAssignStmt"]; + CreateSchemaStmt create_schema_stmt = 140 [json_name="CreateSchemaStmt"]; + AlterTableStmt alter_table_stmt = 141 [json_name="AlterTableStmt"]; + ReplicaIdentityStmt replica_identity_stmt = 142 [json_name="ReplicaIdentityStmt"]; + AlterTableCmd alter_table_cmd = 143 [json_name="AlterTableCmd"]; + AlterCollationStmt alter_collation_stmt = 144 [json_name="AlterCollationStmt"]; + AlterDomainStmt alter_domain_stmt = 145 [json_name="AlterDomainStmt"]; + GrantStmt grant_stmt = 146 [json_name="GrantStmt"]; + ObjectWithArgs object_with_args = 147 [json_name="ObjectWithArgs"]; + AccessPriv access_priv = 148 [json_name="AccessPriv"]; + GrantRoleStmt grant_role_stmt = 149 [json_name="GrantRoleStmt"]; + AlterDefaultPrivilegesStmt alter_default_privileges_stmt = 150 [json_name="AlterDefaultPrivilegesStmt"]; + CopyStmt copy_stmt = 151 [json_name="CopyStmt"]; + VariableSetStmt variable_set_stmt = 152 [json_name="VariableSetStmt"]; + VariableShowStmt variable_show_stmt = 153 [json_name="VariableShowStmt"]; + CreateStmt create_stmt = 154 [json_name="CreateStmt"]; + Constraint constraint = 155 [json_name="Constraint"]; + CreateTableSpaceStmt create_table_space_stmt = 156 [json_name="CreateTableSpaceStmt"]; + DropTableSpaceStmt drop_table_space_stmt = 157 [json_name="DropTableSpaceStmt"]; + AlterTableSpaceOptionsStmt alter_table_space_options_stmt = 158 [json_name="AlterTableSpaceOptionsStmt"]; + AlterTableMoveAllStmt alter_table_move_all_stmt = 159 [json_name="AlterTableMoveAllStmt"]; + CreateExtensionStmt create_extension_stmt = 160 [json_name="CreateExtensionStmt"]; + AlterExtensionStmt alter_extension_stmt = 161 [json_name="AlterExtensionStmt"]; + AlterExtensionContentsStmt alter_extension_contents_stmt = 162 [json_name="AlterExtensionContentsStmt"]; + CreateFdwStmt create_fdw_stmt = 163 [json_name="CreateFdwStmt"]; + AlterFdwStmt alter_fdw_stmt = 164 [json_name="AlterFdwStmt"]; + CreateForeignServerStmt create_foreign_server_stmt = 165 [json_name="CreateForeignServerStmt"]; + AlterForeignServerStmt alter_foreign_server_stmt = 166 [json_name="AlterForeignServerStmt"]; + CreateForeignTableStmt create_foreign_table_stmt = 167 [json_name="CreateForeignTableStmt"]; + CreateUserMappingStmt create_user_mapping_stmt = 168 [json_name="CreateUserMappingStmt"]; + AlterUserMappingStmt alter_user_mapping_stmt = 169 [json_name="AlterUserMappingStmt"]; + DropUserMappingStmt drop_user_mapping_stmt = 170 [json_name="DropUserMappingStmt"]; + ImportForeignSchemaStmt import_foreign_schema_stmt = 171 [json_name="ImportForeignSchemaStmt"]; + CreatePolicyStmt create_policy_stmt = 172 [json_name="CreatePolicyStmt"]; + AlterPolicyStmt alter_policy_stmt = 173 [json_name="AlterPolicyStmt"]; + CreateAmStmt create_am_stmt = 174 [json_name="CreateAmStmt"]; + CreateTrigStmt create_trig_stmt = 175 [json_name="CreateTrigStmt"]; + CreateEventTrigStmt create_event_trig_stmt = 176 [json_name="CreateEventTrigStmt"]; + AlterEventTrigStmt alter_event_trig_stmt = 177 [json_name="AlterEventTrigStmt"]; + CreatePLangStmt create_plang_stmt = 178 [json_name="CreatePLangStmt"]; + CreateRoleStmt create_role_stmt = 179 [json_name="CreateRoleStmt"]; + AlterRoleStmt alter_role_stmt = 180 [json_name="AlterRoleStmt"]; + AlterRoleSetStmt alter_role_set_stmt = 181 [json_name="AlterRoleSetStmt"]; + DropRoleStmt drop_role_stmt = 182 [json_name="DropRoleStmt"]; + CreateSeqStmt create_seq_stmt = 183 [json_name="CreateSeqStmt"]; + AlterSeqStmt alter_seq_stmt = 184 [json_name="AlterSeqStmt"]; + DefineStmt define_stmt = 185 [json_name="DefineStmt"]; + CreateDomainStmt create_domain_stmt = 186 [json_name="CreateDomainStmt"]; + CreateOpClassStmt create_op_class_stmt = 187 [json_name="CreateOpClassStmt"]; + CreateOpClassItem create_op_class_item = 188 [json_name="CreateOpClassItem"]; + CreateOpFamilyStmt create_op_family_stmt = 189 [json_name="CreateOpFamilyStmt"]; + AlterOpFamilyStmt alter_op_family_stmt = 190 [json_name="AlterOpFamilyStmt"]; + DropStmt drop_stmt = 191 [json_name="DropStmt"]; + TruncateStmt truncate_stmt = 192 [json_name="TruncateStmt"]; + CommentStmt comment_stmt = 193 [json_name="CommentStmt"]; + SecLabelStmt sec_label_stmt = 194 [json_name="SecLabelStmt"]; + DeclareCursorStmt declare_cursor_stmt = 195 [json_name="DeclareCursorStmt"]; + ClosePortalStmt close_portal_stmt = 196 [json_name="ClosePortalStmt"]; + FetchStmt fetch_stmt = 197 [json_name="FetchStmt"]; + IndexStmt index_stmt = 198 [json_name="IndexStmt"]; + CreateStatsStmt create_stats_stmt = 199 [json_name="CreateStatsStmt"]; + StatsElem stats_elem = 200 [json_name="StatsElem"]; + AlterStatsStmt alter_stats_stmt = 201 [json_name="AlterStatsStmt"]; + CreateFunctionStmt create_function_stmt = 202 [json_name="CreateFunctionStmt"]; + FunctionParameter function_parameter = 203 [json_name="FunctionParameter"]; + AlterFunctionStmt alter_function_stmt = 204 [json_name="AlterFunctionStmt"]; + DoStmt do_stmt = 205 [json_name="DoStmt"]; + InlineCodeBlock inline_code_block = 206 [json_name="InlineCodeBlock"]; + CallStmt call_stmt = 207 [json_name="CallStmt"]; + CallContext call_context = 208 [json_name="CallContext"]; + RenameStmt rename_stmt = 209 [json_name="RenameStmt"]; + AlterObjectDependsStmt alter_object_depends_stmt = 210 [json_name="AlterObjectDependsStmt"]; + AlterObjectSchemaStmt alter_object_schema_stmt = 211 [json_name="AlterObjectSchemaStmt"]; + AlterOwnerStmt alter_owner_stmt = 212 [json_name="AlterOwnerStmt"]; + AlterOperatorStmt alter_operator_stmt = 213 [json_name="AlterOperatorStmt"]; + AlterTypeStmt alter_type_stmt = 214 [json_name="AlterTypeStmt"]; + RuleStmt rule_stmt = 215 [json_name="RuleStmt"]; + NotifyStmt notify_stmt = 216 [json_name="NotifyStmt"]; + ListenStmt listen_stmt = 217 [json_name="ListenStmt"]; + UnlistenStmt unlisten_stmt = 218 [json_name="UnlistenStmt"]; + TransactionStmt transaction_stmt = 219 [json_name="TransactionStmt"]; + CompositeTypeStmt composite_type_stmt = 220 [json_name="CompositeTypeStmt"]; + CreateEnumStmt create_enum_stmt = 221 [json_name="CreateEnumStmt"]; + CreateRangeStmt create_range_stmt = 222 [json_name="CreateRangeStmt"]; + AlterEnumStmt alter_enum_stmt = 223 [json_name="AlterEnumStmt"]; + ViewStmt view_stmt = 224 [json_name="ViewStmt"]; + LoadStmt load_stmt = 225 [json_name="LoadStmt"]; + CreatedbStmt createdb_stmt = 226 [json_name="CreatedbStmt"]; + AlterDatabaseStmt alter_database_stmt = 227 [json_name="AlterDatabaseStmt"]; + AlterDatabaseRefreshCollStmt alter_database_refresh_coll_stmt = 228 [json_name="AlterDatabaseRefreshCollStmt"]; + AlterDatabaseSetStmt alter_database_set_stmt = 229 [json_name="AlterDatabaseSetStmt"]; + DropdbStmt dropdb_stmt = 230 [json_name="DropdbStmt"]; + AlterSystemStmt alter_system_stmt = 231 [json_name="AlterSystemStmt"]; + ClusterStmt cluster_stmt = 232 [json_name="ClusterStmt"]; + VacuumStmt vacuum_stmt = 233 [json_name="VacuumStmt"]; + VacuumRelation vacuum_relation = 234 [json_name="VacuumRelation"]; + ExplainStmt explain_stmt = 235 [json_name="ExplainStmt"]; + CreateTableAsStmt create_table_as_stmt = 236 [json_name="CreateTableAsStmt"]; + RefreshMatViewStmt refresh_mat_view_stmt = 237 [json_name="RefreshMatViewStmt"]; + CheckPointStmt check_point_stmt = 238 [json_name="CheckPointStmt"]; + DiscardStmt discard_stmt = 239 [json_name="DiscardStmt"]; + LockStmt lock_stmt = 240 [json_name="LockStmt"]; + ConstraintsSetStmt constraints_set_stmt = 241 [json_name="ConstraintsSetStmt"]; + ReindexStmt reindex_stmt = 242 [json_name="ReindexStmt"]; + CreateConversionStmt create_conversion_stmt = 243 [json_name="CreateConversionStmt"]; + CreateCastStmt create_cast_stmt = 244 [json_name="CreateCastStmt"]; + CreateTransformStmt create_transform_stmt = 245 [json_name="CreateTransformStmt"]; + PrepareStmt prepare_stmt = 246 [json_name="PrepareStmt"]; + ExecuteStmt execute_stmt = 247 [json_name="ExecuteStmt"]; + DeallocateStmt deallocate_stmt = 248 [json_name="DeallocateStmt"]; + DropOwnedStmt drop_owned_stmt = 249 [json_name="DropOwnedStmt"]; + ReassignOwnedStmt reassign_owned_stmt = 250 [json_name="ReassignOwnedStmt"]; + AlterTSDictionaryStmt alter_tsdictionary_stmt = 251 [json_name="AlterTSDictionaryStmt"]; + AlterTSConfigurationStmt alter_tsconfiguration_stmt = 252 [json_name="AlterTSConfigurationStmt"]; + PublicationTable publication_table = 253 [json_name="PublicationTable"]; + PublicationObjSpec publication_obj_spec = 254 [json_name="PublicationObjSpec"]; + CreatePublicationStmt create_publication_stmt = 255 [json_name="CreatePublicationStmt"]; + AlterPublicationStmt alter_publication_stmt = 256 [json_name="AlterPublicationStmt"]; + CreateSubscriptionStmt create_subscription_stmt = 257 [json_name="CreateSubscriptionStmt"]; + AlterSubscriptionStmt alter_subscription_stmt = 258 [json_name="AlterSubscriptionStmt"]; + DropSubscriptionStmt drop_subscription_stmt = 259 [json_name="DropSubscriptionStmt"]; + Integer integer = 260 [json_name="Integer"]; + Float float = 261 [json_name="Float"]; + Boolean boolean = 262 [json_name="Boolean"]; + String string = 263 [json_name="String"]; + BitString bit_string = 264 [json_name="BitString"]; + List list = 265 [json_name="List"]; + IntList int_list = 266 [json_name="IntList"]; + OidList oid_list = 267 [json_name="OidList"]; + A_Const a_const = 268 [json_name="A_Const"]; + } +} + +message Integer +{ + int32 ival = 1; /* machine integer */ +} + +message Float +{ + string fval = 1; /* string */ +} + +message Boolean +{ + bool boolval = 1; +} + +message String +{ + string sval = 1; /* string */ +} + +message BitString +{ + string bsval = 1; /* string */ +} + +message List +{ + repeated Node items = 1; +} + +message OidList +{ + repeated Node items = 1; +} + +message IntList +{ + repeated Node items = 1; +} + +message A_Const +{ + oneof val { + Integer ival = 1; + Float fval = 2; + Boolean boolval = 3; + String sval = 4; + BitString bsval = 5; + } + bool isnull = 10; + int32 location = 11; +} + +message Alias +{ + string aliasname = 1 [json_name="aliasname"]; + repeated Node colnames = 2 [json_name="colnames"]; +} + +message RangeVar +{ + string catalogname = 1 [json_name="catalogname"]; + string schemaname = 2 [json_name="schemaname"]; + string relname = 3 [json_name="relname"]; + bool inh = 4 [json_name="inh"]; + string relpersistence = 5 [json_name="relpersistence"]; + Alias alias = 6 [json_name="alias"]; + int32 location = 7 [json_name="location"]; +} + +message TableFunc +{ + TableFuncType functype = 1 [json_name="functype"]; + repeated Node ns_uris = 2 [json_name="ns_uris"]; + repeated Node ns_names = 3 [json_name="ns_names"]; + Node docexpr = 4 [json_name="docexpr"]; + Node rowexpr = 5 [json_name="rowexpr"]; + repeated Node colnames = 6 [json_name="colnames"]; + repeated Node coltypes = 7 [json_name="coltypes"]; + repeated Node coltypmods = 8 [json_name="coltypmods"]; + repeated Node colcollations = 9 [json_name="colcollations"]; + repeated Node colexprs = 10 [json_name="colexprs"]; + repeated Node coldefexprs = 11 [json_name="coldefexprs"]; + repeated Node colvalexprs = 12 [json_name="colvalexprs"]; + repeated Node passingvalexprs = 13 [json_name="passingvalexprs"]; + repeated uint64 notnulls = 14 [json_name="notnulls"]; + Node plan = 15 [json_name="plan"]; + int32 ordinalitycol = 16 [json_name="ordinalitycol"]; + int32 location = 17 [json_name="location"]; +} + +message IntoClause +{ + RangeVar rel = 1 [json_name="rel"]; + repeated Node col_names = 2 [json_name="colNames"]; + string access_method = 3 [json_name="accessMethod"]; + repeated Node options = 4 [json_name="options"]; + OnCommitAction on_commit = 5 [json_name="onCommit"]; + string table_space_name = 6 [json_name="tableSpaceName"]; + Node view_query = 7 [json_name="viewQuery"]; + bool skip_data = 8 [json_name="skipData"]; +} + +message Var +{ + Node xpr = 1 [json_name="xpr"]; + int32 varno = 2 [json_name="varno"]; + int32 varattno = 3 [json_name="varattno"]; + uint32 vartype = 4 [json_name="vartype"]; + int32 vartypmod = 5 [json_name="vartypmod"]; + uint32 varcollid = 6 [json_name="varcollid"]; + repeated uint64 varnullingrels = 7 [json_name="varnullingrels"]; + uint32 varlevelsup = 8 [json_name="varlevelsup"]; + int32 location = 9 [json_name="location"]; +} + +message Param +{ + Node xpr = 1 [json_name="xpr"]; + ParamKind paramkind = 2 [json_name="paramkind"]; + int32 paramid = 3 [json_name="paramid"]; + uint32 paramtype = 4 [json_name="paramtype"]; + int32 paramtypmod = 5 [json_name="paramtypmod"]; + uint32 paramcollid = 6 [json_name="paramcollid"]; + int32 location = 7 [json_name="location"]; +} + +message Aggref +{ + Node xpr = 1 [json_name="xpr"]; + uint32 aggfnoid = 2 [json_name="aggfnoid"]; + uint32 aggtype = 3 [json_name="aggtype"]; + uint32 aggcollid = 4 [json_name="aggcollid"]; + uint32 inputcollid = 5 [json_name="inputcollid"]; + repeated Node aggargtypes = 6 [json_name="aggargtypes"]; + repeated Node aggdirectargs = 7 [json_name="aggdirectargs"]; + repeated Node args = 8 [json_name="args"]; + repeated Node aggorder = 9 [json_name="aggorder"]; + repeated Node aggdistinct = 10 [json_name="aggdistinct"]; + Node aggfilter = 11 [json_name="aggfilter"]; + bool aggstar = 12 [json_name="aggstar"]; + bool aggvariadic = 13 [json_name="aggvariadic"]; + string aggkind = 14 [json_name="aggkind"]; + uint32 agglevelsup = 15 [json_name="agglevelsup"]; + AggSplit aggsplit = 16 [json_name="aggsplit"]; + int32 aggno = 17 [json_name="aggno"]; + int32 aggtransno = 18 [json_name="aggtransno"]; + int32 location = 19 [json_name="location"]; +} + +message GroupingFunc +{ + Node xpr = 1 [json_name="xpr"]; + repeated Node args = 2 [json_name="args"]; + repeated Node refs = 3 [json_name="refs"]; + uint32 agglevelsup = 4 [json_name="agglevelsup"]; + int32 location = 5 [json_name="location"]; +} + +message WindowFunc +{ + Node xpr = 1 [json_name="xpr"]; + uint32 winfnoid = 2 [json_name="winfnoid"]; + uint32 wintype = 3 [json_name="wintype"]; + uint32 wincollid = 4 [json_name="wincollid"]; + uint32 inputcollid = 5 [json_name="inputcollid"]; + repeated Node args = 6 [json_name="args"]; + Node aggfilter = 7 [json_name="aggfilter"]; + repeated Node run_condition = 8 [json_name="runCondition"]; + uint32 winref = 9 [json_name="winref"]; + bool winstar = 10 [json_name="winstar"]; + bool winagg = 11 [json_name="winagg"]; + int32 location = 12 [json_name="location"]; +} + +message WindowFuncRunCondition +{ + Node xpr = 1 [json_name="xpr"]; + uint32 opno = 2 [json_name="opno"]; + uint32 inputcollid = 3 [json_name="inputcollid"]; + bool wfunc_left = 4 [json_name="wfunc_left"]; + Node arg = 5 [json_name="arg"]; +} + +message MergeSupportFunc +{ + Node xpr = 1 [json_name="xpr"]; + uint32 msftype = 2 [json_name="msftype"]; + uint32 msfcollid = 3 [json_name="msfcollid"]; + int32 location = 4 [json_name="location"]; +} + +message SubscriptingRef +{ + Node xpr = 1 [json_name="xpr"]; + uint32 refcontainertype = 2 [json_name="refcontainertype"]; + uint32 refelemtype = 3 [json_name="refelemtype"]; + uint32 refrestype = 4 [json_name="refrestype"]; + int32 reftypmod = 5 [json_name="reftypmod"]; + uint32 refcollid = 6 [json_name="refcollid"]; + repeated Node refupperindexpr = 7 [json_name="refupperindexpr"]; + repeated Node reflowerindexpr = 8 [json_name="reflowerindexpr"]; + Node refexpr = 9 [json_name="refexpr"]; + Node refassgnexpr = 10 [json_name="refassgnexpr"]; +} + +message FuncExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 funcid = 2 [json_name="funcid"]; + uint32 funcresulttype = 3 [json_name="funcresulttype"]; + bool funcretset = 4 [json_name="funcretset"]; + bool funcvariadic = 5 [json_name="funcvariadic"]; + CoercionForm funcformat = 6 [json_name="funcformat"]; + uint32 funccollid = 7 [json_name="funccollid"]; + uint32 inputcollid = 8 [json_name="inputcollid"]; + repeated Node args = 9 [json_name="args"]; + int32 location = 10 [json_name="location"]; +} + +message NamedArgExpr +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + string name = 3 [json_name="name"]; + int32 argnumber = 4 [json_name="argnumber"]; + int32 location = 5 [json_name="location"]; +} + +message OpExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 opno = 2 [json_name="opno"]; + uint32 opresulttype = 3 [json_name="opresulttype"]; + bool opretset = 4 [json_name="opretset"]; + uint32 opcollid = 5 [json_name="opcollid"]; + uint32 inputcollid = 6 [json_name="inputcollid"]; + repeated Node args = 7 [json_name="args"]; + int32 location = 8 [json_name="location"]; +} + +message DistinctExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 opno = 2 [json_name="opno"]; + uint32 opresulttype = 3 [json_name="opresulttype"]; + bool opretset = 4 [json_name="opretset"]; + uint32 opcollid = 5 [json_name="opcollid"]; + uint32 inputcollid = 6 [json_name="inputcollid"]; + repeated Node args = 7 [json_name="args"]; + int32 location = 8 [json_name="location"]; +} + +message NullIfExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 opno = 2 [json_name="opno"]; + uint32 opresulttype = 3 [json_name="opresulttype"]; + bool opretset = 4 [json_name="opretset"]; + uint32 opcollid = 5 [json_name="opcollid"]; + uint32 inputcollid = 6 [json_name="inputcollid"]; + repeated Node args = 7 [json_name="args"]; + int32 location = 8 [json_name="location"]; +} + +message ScalarArrayOpExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 opno = 2 [json_name="opno"]; + bool use_or = 3 [json_name="useOr"]; + uint32 inputcollid = 4 [json_name="inputcollid"]; + repeated Node args = 5 [json_name="args"]; + int32 location = 6 [json_name="location"]; +} + +message BoolExpr +{ + Node xpr = 1 [json_name="xpr"]; + BoolExprType boolop = 2 [json_name="boolop"]; + repeated Node args = 3 [json_name="args"]; + int32 location = 4 [json_name="location"]; +} + +message SubLink +{ + Node xpr = 1 [json_name="xpr"]; + SubLinkType sub_link_type = 2 [json_name="subLinkType"]; + int32 sub_link_id = 3 [json_name="subLinkId"]; + Node testexpr = 4 [json_name="testexpr"]; + repeated Node oper_name = 5 [json_name="operName"]; + Node subselect = 6 [json_name="subselect"]; + int32 location = 7 [json_name="location"]; +} + +message SubPlan +{ + Node xpr = 1 [json_name="xpr"]; + SubLinkType sub_link_type = 2 [json_name="subLinkType"]; + Node testexpr = 3 [json_name="testexpr"]; + repeated Node param_ids = 4 [json_name="paramIds"]; + int32 plan_id = 5 [json_name="plan_id"]; + string plan_name = 6 [json_name="plan_name"]; + uint32 first_col_type = 7 [json_name="firstColType"]; + int32 first_col_typmod = 8 [json_name="firstColTypmod"]; + uint32 first_col_collation = 9 [json_name="firstColCollation"]; + bool use_hash_table = 10 [json_name="useHashTable"]; + bool unknown_eq_false = 11 [json_name="unknownEqFalse"]; + bool parallel_safe = 12 [json_name="parallel_safe"]; + repeated Node set_param = 13 [json_name="setParam"]; + repeated Node par_param = 14 [json_name="parParam"]; + repeated Node args = 15 [json_name="args"]; + double startup_cost = 16 [json_name="startup_cost"]; + double per_call_cost = 17 [json_name="per_call_cost"]; +} + +message AlternativeSubPlan +{ + Node xpr = 1 [json_name="xpr"]; + repeated Node subplans = 2 [json_name="subplans"]; +} + +message FieldSelect +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + int32 fieldnum = 3 [json_name="fieldnum"]; + uint32 resulttype = 4 [json_name="resulttype"]; + int32 resulttypmod = 5 [json_name="resulttypmod"]; + uint32 resultcollid = 6 [json_name="resultcollid"]; +} + +message FieldStore +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + repeated Node newvals = 3 [json_name="newvals"]; + repeated Node fieldnums = 4 [json_name="fieldnums"]; + uint32 resulttype = 5 [json_name="resulttype"]; +} + +message RelabelType +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + uint32 resulttype = 3 [json_name="resulttype"]; + int32 resulttypmod = 4 [json_name="resulttypmod"]; + uint32 resultcollid = 5 [json_name="resultcollid"]; + CoercionForm relabelformat = 6 [json_name="relabelformat"]; + int32 location = 7 [json_name="location"]; +} + +message CoerceViaIO +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + uint32 resulttype = 3 [json_name="resulttype"]; + uint32 resultcollid = 4 [json_name="resultcollid"]; + CoercionForm coerceformat = 5 [json_name="coerceformat"]; + int32 location = 6 [json_name="location"]; +} + +message ArrayCoerceExpr +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + Node elemexpr = 3 [json_name="elemexpr"]; + uint32 resulttype = 4 [json_name="resulttype"]; + int32 resulttypmod = 5 [json_name="resulttypmod"]; + uint32 resultcollid = 6 [json_name="resultcollid"]; + CoercionForm coerceformat = 7 [json_name="coerceformat"]; + int32 location = 8 [json_name="location"]; +} + +message ConvertRowtypeExpr +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + uint32 resulttype = 3 [json_name="resulttype"]; + CoercionForm convertformat = 4 [json_name="convertformat"]; + int32 location = 5 [json_name="location"]; +} + +message CollateExpr +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + uint32 coll_oid = 3 [json_name="collOid"]; + int32 location = 4 [json_name="location"]; +} + +message CaseExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 casetype = 2 [json_name="casetype"]; + uint32 casecollid = 3 [json_name="casecollid"]; + Node arg = 4 [json_name="arg"]; + repeated Node args = 5 [json_name="args"]; + Node defresult = 6 [json_name="defresult"]; + int32 location = 7 [json_name="location"]; +} + +message CaseWhen +{ + Node xpr = 1 [json_name="xpr"]; + Node expr = 2 [json_name="expr"]; + Node result = 3 [json_name="result"]; + int32 location = 4 [json_name="location"]; +} + +message CaseTestExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 type_id = 2 [json_name="typeId"]; + int32 type_mod = 3 [json_name="typeMod"]; + uint32 collation = 4 [json_name="collation"]; +} + +message ArrayExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 array_typeid = 2 [json_name="array_typeid"]; + uint32 array_collid = 3 [json_name="array_collid"]; + uint32 element_typeid = 4 [json_name="element_typeid"]; + repeated Node elements = 5 [json_name="elements"]; + bool multidims = 6 [json_name="multidims"]; + int32 location = 7 [json_name="location"]; +} + +message RowExpr +{ + Node xpr = 1 [json_name="xpr"]; + repeated Node args = 2 [json_name="args"]; + uint32 row_typeid = 3 [json_name="row_typeid"]; + CoercionForm row_format = 4 [json_name="row_format"]; + repeated Node colnames = 5 [json_name="colnames"]; + int32 location = 6 [json_name="location"]; +} + +message RowCompareExpr +{ + Node xpr = 1 [json_name="xpr"]; + RowCompareType rctype = 2 [json_name="rctype"]; + repeated Node opnos = 3 [json_name="opnos"]; + repeated Node opfamilies = 4 [json_name="opfamilies"]; + repeated Node inputcollids = 5 [json_name="inputcollids"]; + repeated Node largs = 6 [json_name="largs"]; + repeated Node rargs = 7 [json_name="rargs"]; +} + +message CoalesceExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 coalescetype = 2 [json_name="coalescetype"]; + uint32 coalescecollid = 3 [json_name="coalescecollid"]; + repeated Node args = 4 [json_name="args"]; + int32 location = 5 [json_name="location"]; +} + +message MinMaxExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 minmaxtype = 2 [json_name="minmaxtype"]; + uint32 minmaxcollid = 3 [json_name="minmaxcollid"]; + uint32 inputcollid = 4 [json_name="inputcollid"]; + MinMaxOp op = 5 [json_name="op"]; + repeated Node args = 6 [json_name="args"]; + int32 location = 7 [json_name="location"]; +} + +message SQLValueFunction +{ + Node xpr = 1 [json_name="xpr"]; + SQLValueFunctionOp op = 2 [json_name="op"]; + uint32 type = 3 [json_name="type"]; + int32 typmod = 4 [json_name="typmod"]; + int32 location = 5 [json_name="location"]; +} + +message XmlExpr +{ + Node xpr = 1 [json_name="xpr"]; + XmlExprOp op = 2 [json_name="op"]; + string name = 3 [json_name="name"]; + repeated Node named_args = 4 [json_name="named_args"]; + repeated Node arg_names = 5 [json_name="arg_names"]; + repeated Node args = 6 [json_name="args"]; + XmlOptionType xmloption = 7 [json_name="xmloption"]; + bool indent = 8 [json_name="indent"]; + uint32 type = 9 [json_name="type"]; + int32 typmod = 10 [json_name="typmod"]; + int32 location = 11 [json_name="location"]; +} + +message JsonFormat +{ + JsonFormatType format_type = 1 [json_name="format_type"]; + JsonEncoding encoding = 2 [json_name="encoding"]; + int32 location = 3 [json_name="location"]; +} + +message JsonReturning +{ + JsonFormat format = 1 [json_name="format"]; + uint32 typid = 2 [json_name="typid"]; + int32 typmod = 3 [json_name="typmod"]; +} + +message JsonValueExpr +{ + Node raw_expr = 1 [json_name="raw_expr"]; + Node formatted_expr = 2 [json_name="formatted_expr"]; + JsonFormat format = 3 [json_name="format"]; +} + +message JsonConstructorExpr +{ + Node xpr = 1 [json_name="xpr"]; + JsonConstructorType type = 2 [json_name="type"]; + repeated Node args = 3 [json_name="args"]; + Node func = 4 [json_name="func"]; + Node coercion = 5 [json_name="coercion"]; + JsonReturning returning = 6 [json_name="returning"]; + bool absent_on_null = 7 [json_name="absent_on_null"]; + bool unique = 8 [json_name="unique"]; + int32 location = 9 [json_name="location"]; +} + +message JsonIsPredicate +{ + Node expr = 1 [json_name="expr"]; + JsonFormat format = 2 [json_name="format"]; + JsonValueType item_type = 3 [json_name="item_type"]; + bool unique_keys = 4 [json_name="unique_keys"]; + int32 location = 5 [json_name="location"]; +} + +message JsonBehavior +{ + JsonBehaviorType btype = 1 [json_name="btype"]; + Node expr = 2 [json_name="expr"]; + bool coerce = 3 [json_name="coerce"]; + int32 location = 4 [json_name="location"]; +} + +message JsonExpr +{ + Node xpr = 1 [json_name="xpr"]; + JsonExprOp op = 2 [json_name="op"]; + string column_name = 3 [json_name="column_name"]; + Node formatted_expr = 4 [json_name="formatted_expr"]; + JsonFormat format = 5 [json_name="format"]; + Node path_spec = 6 [json_name="path_spec"]; + JsonReturning returning = 7 [json_name="returning"]; + repeated Node passing_names = 8 [json_name="passing_names"]; + repeated Node passing_values = 9 [json_name="passing_values"]; + JsonBehavior on_empty = 10 [json_name="on_empty"]; + JsonBehavior on_error = 11 [json_name="on_error"]; + bool use_io_coercion = 12 [json_name="use_io_coercion"]; + bool use_json_coercion = 13 [json_name="use_json_coercion"]; + JsonWrapper wrapper = 14 [json_name="wrapper"]; + bool omit_quotes = 15 [json_name="omit_quotes"]; + uint32 collation = 16 [json_name="collation"]; + int32 location = 17 [json_name="location"]; +} + +message JsonTablePath +{ + string name = 1 [json_name="name"]; +} + +message JsonTablePathScan +{ + Node plan = 1 [json_name="plan"]; + JsonTablePath path = 2 [json_name="path"]; + bool error_on_error = 3 [json_name="errorOnError"]; + Node child = 4 [json_name="child"]; + int32 col_min = 5 [json_name="colMin"]; + int32 col_max = 6 [json_name="colMax"]; +} + +message JsonTableSiblingJoin +{ + Node plan = 1 [json_name="plan"]; + Node lplan = 2 [json_name="lplan"]; + Node rplan = 3 [json_name="rplan"]; +} + +message NullTest +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + NullTestType nulltesttype = 3 [json_name="nulltesttype"]; + bool argisrow = 4 [json_name="argisrow"]; + int32 location = 5 [json_name="location"]; +} + +message BooleanTest +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + BoolTestType booltesttype = 3 [json_name="booltesttype"]; + int32 location = 4 [json_name="location"]; +} + +message MergeAction +{ + MergeMatchKind match_kind = 1 [json_name="matchKind"]; + CmdType command_type = 2 [json_name="commandType"]; + OverridingKind override = 3 [json_name="override"]; + Node qual = 4 [json_name="qual"]; + repeated Node target_list = 5 [json_name="targetList"]; + repeated Node update_colnos = 6 [json_name="updateColnos"]; +} + +message CoerceToDomain +{ + Node xpr = 1 [json_name="xpr"]; + Node arg = 2 [json_name="arg"]; + uint32 resulttype = 3 [json_name="resulttype"]; + int32 resulttypmod = 4 [json_name="resulttypmod"]; + uint32 resultcollid = 5 [json_name="resultcollid"]; + CoercionForm coercionformat = 6 [json_name="coercionformat"]; + int32 location = 7 [json_name="location"]; +} + +message CoerceToDomainValue +{ + Node xpr = 1 [json_name="xpr"]; + uint32 type_id = 2 [json_name="typeId"]; + int32 type_mod = 3 [json_name="typeMod"]; + uint32 collation = 4 [json_name="collation"]; + int32 location = 5 [json_name="location"]; +} + +message SetToDefault +{ + Node xpr = 1 [json_name="xpr"]; + uint32 type_id = 2 [json_name="typeId"]; + int32 type_mod = 3 [json_name="typeMod"]; + uint32 collation = 4 [json_name="collation"]; + int32 location = 5 [json_name="location"]; +} + +message CurrentOfExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 cvarno = 2 [json_name="cvarno"]; + string cursor_name = 3 [json_name="cursor_name"]; + int32 cursor_param = 4 [json_name="cursor_param"]; +} + +message NextValueExpr +{ + Node xpr = 1 [json_name="xpr"]; + uint32 seqid = 2 [json_name="seqid"]; + uint32 type_id = 3 [json_name="typeId"]; +} + +message InferenceElem +{ + Node xpr = 1 [json_name="xpr"]; + Node expr = 2 [json_name="expr"]; + uint32 infercollid = 3 [json_name="infercollid"]; + uint32 inferopclass = 4 [json_name="inferopclass"]; +} + +message TargetEntry +{ + Node xpr = 1 [json_name="xpr"]; + Node expr = 2 [json_name="expr"]; + int32 resno = 3 [json_name="resno"]; + string resname = 4 [json_name="resname"]; + uint32 ressortgroupref = 5 [json_name="ressortgroupref"]; + uint32 resorigtbl = 6 [json_name="resorigtbl"]; + int32 resorigcol = 7 [json_name="resorigcol"]; + bool resjunk = 8 [json_name="resjunk"]; +} + +message RangeTblRef +{ + int32 rtindex = 1 [json_name="rtindex"]; +} + +message JoinExpr +{ + JoinType jointype = 1 [json_name="jointype"]; + bool is_natural = 2 [json_name="isNatural"]; + Node larg = 3 [json_name="larg"]; + Node rarg = 4 [json_name="rarg"]; + repeated Node using_clause = 5 [json_name="usingClause"]; + Alias join_using_alias = 6 [json_name="join_using_alias"]; + Node quals = 7 [json_name="quals"]; + Alias alias = 8 [json_name="alias"]; + int32 rtindex = 9 [json_name="rtindex"]; +} + +message FromExpr +{ + repeated Node fromlist = 1 [json_name="fromlist"]; + Node quals = 2 [json_name="quals"]; +} + +message OnConflictExpr +{ + OnConflictAction action = 1 [json_name="action"]; + repeated Node arbiter_elems = 2 [json_name="arbiterElems"]; + Node arbiter_where = 3 [json_name="arbiterWhere"]; + uint32 constraint = 4 [json_name="constraint"]; + repeated Node on_conflict_set = 5 [json_name="onConflictSet"]; + Node on_conflict_where = 6 [json_name="onConflictWhere"]; + int32 excl_rel_index = 7 [json_name="exclRelIndex"]; + repeated Node excl_rel_tlist = 8 [json_name="exclRelTlist"]; +} + +message Query +{ + CmdType command_type = 1 [json_name="commandType"]; + QuerySource query_source = 2 [json_name="querySource"]; + bool can_set_tag = 3 [json_name="canSetTag"]; + Node utility_stmt = 4 [json_name="utilityStmt"]; + int32 result_relation = 5 [json_name="resultRelation"]; + bool has_aggs = 6 [json_name="hasAggs"]; + bool has_window_funcs = 7 [json_name="hasWindowFuncs"]; + bool has_target_srfs = 8 [json_name="hasTargetSRFs"]; + bool has_sub_links = 9 [json_name="hasSubLinks"]; + bool has_distinct_on = 10 [json_name="hasDistinctOn"]; + bool has_recursive = 11 [json_name="hasRecursive"]; + bool has_modifying_cte = 12 [json_name="hasModifyingCTE"]; + bool has_for_update = 13 [json_name="hasForUpdate"]; + bool has_row_security = 14 [json_name="hasRowSecurity"]; + bool is_return = 15 [json_name="isReturn"]; + repeated Node cte_list = 16 [json_name="cteList"]; + repeated Node rtable = 17 [json_name="rtable"]; + repeated Node rteperminfos = 18 [json_name="rteperminfos"]; + FromExpr jointree = 19 [json_name="jointree"]; + repeated Node merge_action_list = 20 [json_name="mergeActionList"]; + int32 merge_target_relation = 21 [json_name="mergeTargetRelation"]; + Node merge_join_condition = 22 [json_name="mergeJoinCondition"]; + repeated Node target_list = 23 [json_name="targetList"]; + OverridingKind override = 24 [json_name="override"]; + OnConflictExpr on_conflict = 25 [json_name="onConflict"]; + repeated Node returning_list = 26 [json_name="returningList"]; + repeated Node group_clause = 27 [json_name="groupClause"]; + bool group_distinct = 28 [json_name="groupDistinct"]; + repeated Node grouping_sets = 29 [json_name="groupingSets"]; + Node having_qual = 30 [json_name="havingQual"]; + repeated Node window_clause = 31 [json_name="windowClause"]; + repeated Node distinct_clause = 32 [json_name="distinctClause"]; + repeated Node sort_clause = 33 [json_name="sortClause"]; + Node limit_offset = 34 [json_name="limitOffset"]; + Node limit_count = 35 [json_name="limitCount"]; + LimitOption limit_option = 36 [json_name="limitOption"]; + repeated Node row_marks = 37 [json_name="rowMarks"]; + Node set_operations = 38 [json_name="setOperations"]; + repeated Node constraint_deps = 39 [json_name="constraintDeps"]; + repeated Node with_check_options = 40 [json_name="withCheckOptions"]; + int32 stmt_location = 41 [json_name="stmt_location"]; + int32 stmt_len = 42 [json_name="stmt_len"]; +} + +message TypeName +{ + repeated Node names = 1 [json_name="names"]; + uint32 type_oid = 2 [json_name="typeOid"]; + bool setof = 3 [json_name="setof"]; + bool pct_type = 4 [json_name="pct_type"]; + repeated Node typmods = 5 [json_name="typmods"]; + int32 typemod = 6 [json_name="typemod"]; + repeated Node array_bounds = 7 [json_name="arrayBounds"]; + int32 location = 8 [json_name="location"]; +} + +message ColumnRef +{ + repeated Node fields = 1 [json_name="fields"]; + int32 location = 2 [json_name="location"]; +} + +message ParamRef +{ + int32 number = 1 [json_name="number"]; + int32 location = 2 [json_name="location"]; +} + +message A_Expr +{ + A_Expr_Kind kind = 1 [json_name="kind"]; + repeated Node name = 2 [json_name="name"]; + Node lexpr = 3 [json_name="lexpr"]; + Node rexpr = 4 [json_name="rexpr"]; + int32 location = 5 [json_name="location"]; +} + +message TypeCast +{ + Node arg = 1 [json_name="arg"]; + TypeName type_name = 2 [json_name="typeName"]; + int32 location = 3 [json_name="location"]; +} + +message CollateClause +{ + Node arg = 1 [json_name="arg"]; + repeated Node collname = 2 [json_name="collname"]; + int32 location = 3 [json_name="location"]; +} + +message RoleSpec +{ + RoleSpecType roletype = 1 [json_name="roletype"]; + string rolename = 2 [json_name="rolename"]; + int32 location = 3 [json_name="location"]; +} + +message FuncCall +{ + repeated Node funcname = 1 [json_name="funcname"]; + repeated Node args = 2 [json_name="args"]; + repeated Node agg_order = 3 [json_name="agg_order"]; + Node agg_filter = 4 [json_name="agg_filter"]; + WindowDef over = 5 [json_name="over"]; + bool agg_within_group = 6 [json_name="agg_within_group"]; + bool agg_star = 7 [json_name="agg_star"]; + bool agg_distinct = 8 [json_name="agg_distinct"]; + bool func_variadic = 9 [json_name="func_variadic"]; + CoercionForm funcformat = 10 [json_name="funcformat"]; + int32 location = 11 [json_name="location"]; +} + +message A_Star +{ +} + +message A_Indices +{ + bool is_slice = 1 [json_name="is_slice"]; + Node lidx = 2 [json_name="lidx"]; + Node uidx = 3 [json_name="uidx"]; +} + +message A_Indirection +{ + Node arg = 1 [json_name="arg"]; + repeated Node indirection = 2 [json_name="indirection"]; +} + +message A_ArrayExpr +{ + repeated Node elements = 1 [json_name="elements"]; + int32 location = 2 [json_name="location"]; +} + +message ResTarget +{ + string name = 1 [json_name="name"]; + repeated Node indirection = 2 [json_name="indirection"]; + Node val = 3 [json_name="val"]; + int32 location = 4 [json_name="location"]; +} + +message MultiAssignRef +{ + Node source = 1 [json_name="source"]; + int32 colno = 2 [json_name="colno"]; + int32 ncolumns = 3 [json_name="ncolumns"]; +} + +message SortBy +{ + Node node = 1 [json_name="node"]; + SortByDir sortby_dir = 2 [json_name="sortby_dir"]; + SortByNulls sortby_nulls = 3 [json_name="sortby_nulls"]; + repeated Node use_op = 4 [json_name="useOp"]; + int32 location = 5 [json_name="location"]; +} + +message WindowDef +{ + string name = 1 [json_name="name"]; + string refname = 2 [json_name="refname"]; + repeated Node partition_clause = 3 [json_name="partitionClause"]; + repeated Node order_clause = 4 [json_name="orderClause"]; + int32 frame_options = 5 [json_name="frameOptions"]; + Node start_offset = 6 [json_name="startOffset"]; + Node end_offset = 7 [json_name="endOffset"]; + int32 location = 8 [json_name="location"]; +} + +message RangeSubselect +{ + bool lateral = 1 [json_name="lateral"]; + Node subquery = 2 [json_name="subquery"]; + Alias alias = 3 [json_name="alias"]; +} + +message RangeFunction +{ + bool lateral = 1 [json_name="lateral"]; + bool ordinality = 2 [json_name="ordinality"]; + bool is_rowsfrom = 3 [json_name="is_rowsfrom"]; + repeated Node functions = 4 [json_name="functions"]; + Alias alias = 5 [json_name="alias"]; + repeated Node coldeflist = 6 [json_name="coldeflist"]; +} + +message RangeTableFunc +{ + bool lateral = 1 [json_name="lateral"]; + Node docexpr = 2 [json_name="docexpr"]; + Node rowexpr = 3 [json_name="rowexpr"]; + repeated Node namespaces = 4 [json_name="namespaces"]; + repeated Node columns = 5 [json_name="columns"]; + Alias alias = 6 [json_name="alias"]; + int32 location = 7 [json_name="location"]; +} + +message RangeTableFuncCol +{ + string colname = 1 [json_name="colname"]; + TypeName type_name = 2 [json_name="typeName"]; + bool for_ordinality = 3 [json_name="for_ordinality"]; + bool is_not_null = 4 [json_name="is_not_null"]; + Node colexpr = 5 [json_name="colexpr"]; + Node coldefexpr = 6 [json_name="coldefexpr"]; + int32 location = 7 [json_name="location"]; +} + +message RangeTableSample +{ + Node relation = 1 [json_name="relation"]; + repeated Node method = 2 [json_name="method"]; + repeated Node args = 3 [json_name="args"]; + Node repeatable = 4 [json_name="repeatable"]; + int32 location = 5 [json_name="location"]; +} + +message ColumnDef +{ + string colname = 1 [json_name="colname"]; + TypeName type_name = 2 [json_name="typeName"]; + string compression = 3 [json_name="compression"]; + int32 inhcount = 4 [json_name="inhcount"]; + bool is_local = 5 [json_name="is_local"]; + bool is_not_null = 6 [json_name="is_not_null"]; + bool is_from_type = 7 [json_name="is_from_type"]; + string storage = 8 [json_name="storage"]; + string storage_name = 9 [json_name="storage_name"]; + Node raw_default = 10 [json_name="raw_default"]; + Node cooked_default = 11 [json_name="cooked_default"]; + string identity = 12 [json_name="identity"]; + RangeVar identity_sequence = 13 [json_name="identitySequence"]; + string generated = 14 [json_name="generated"]; + CollateClause coll_clause = 15 [json_name="collClause"]; + uint32 coll_oid = 16 [json_name="collOid"]; + repeated Node constraints = 17 [json_name="constraints"]; + repeated Node fdwoptions = 18 [json_name="fdwoptions"]; + int32 location = 19 [json_name="location"]; +} + +message TableLikeClause +{ + RangeVar relation = 1 [json_name="relation"]; + uint32 options = 2 [json_name="options"]; + uint32 relation_oid = 3 [json_name="relationOid"]; +} + +message IndexElem +{ + string name = 1 [json_name="name"]; + Node expr = 2 [json_name="expr"]; + string indexcolname = 3 [json_name="indexcolname"]; + repeated Node collation = 4 [json_name="collation"]; + repeated Node opclass = 5 [json_name="opclass"]; + repeated Node opclassopts = 6 [json_name="opclassopts"]; + SortByDir ordering = 7 [json_name="ordering"]; + SortByNulls nulls_ordering = 8 [json_name="nulls_ordering"]; +} + +message DefElem +{ + string defnamespace = 1 [json_name="defnamespace"]; + string defname = 2 [json_name="defname"]; + Node arg = 3 [json_name="arg"]; + DefElemAction defaction = 4 [json_name="defaction"]; + int32 location = 5 [json_name="location"]; +} + +message LockingClause +{ + repeated Node locked_rels = 1 [json_name="lockedRels"]; + LockClauseStrength strength = 2 [json_name="strength"]; + LockWaitPolicy wait_policy = 3 [json_name="waitPolicy"]; +} + +message XmlSerialize +{ + XmlOptionType xmloption = 1 [json_name="xmloption"]; + Node expr = 2 [json_name="expr"]; + TypeName type_name = 3 [json_name="typeName"]; + bool indent = 4 [json_name="indent"]; + int32 location = 5 [json_name="location"]; +} + +message PartitionElem +{ + string name = 1 [json_name="name"]; + Node expr = 2 [json_name="expr"]; + repeated Node collation = 3 [json_name="collation"]; + repeated Node opclass = 4 [json_name="opclass"]; + int32 location = 5 [json_name="location"]; +} + +message PartitionSpec +{ + PartitionStrategy strategy = 1 [json_name="strategy"]; + repeated Node part_params = 2 [json_name="partParams"]; + int32 location = 3 [json_name="location"]; +} + +message PartitionBoundSpec +{ + string strategy = 1 [json_name="strategy"]; + bool is_default = 2 [json_name="is_default"]; + int32 modulus = 3 [json_name="modulus"]; + int32 remainder = 4 [json_name="remainder"]; + repeated Node listdatums = 5 [json_name="listdatums"]; + repeated Node lowerdatums = 6 [json_name="lowerdatums"]; + repeated Node upperdatums = 7 [json_name="upperdatums"]; + int32 location = 8 [json_name="location"]; +} + +message PartitionRangeDatum +{ + PartitionRangeDatumKind kind = 1 [json_name="kind"]; + Node value = 2 [json_name="value"]; + int32 location = 3 [json_name="location"]; +} + +message SinglePartitionSpec +{ +} + +message PartitionCmd +{ + RangeVar name = 1 [json_name="name"]; + PartitionBoundSpec bound = 2 [json_name="bound"]; + bool concurrent = 3 [json_name="concurrent"]; +} + +message RangeTblEntry +{ + Alias alias = 1 [json_name="alias"]; + Alias eref = 2 [json_name="eref"]; + RTEKind rtekind = 3 [json_name="rtekind"]; + uint32 relid = 4 [json_name="relid"]; + bool inh = 5 [json_name="inh"]; + string relkind = 6 [json_name="relkind"]; + int32 rellockmode = 7 [json_name="rellockmode"]; + uint32 perminfoindex = 8 [json_name="perminfoindex"]; + TableSampleClause tablesample = 9 [json_name="tablesample"]; + Query subquery = 10 [json_name="subquery"]; + bool security_barrier = 11 [json_name="security_barrier"]; + JoinType jointype = 12 [json_name="jointype"]; + int32 joinmergedcols = 13 [json_name="joinmergedcols"]; + repeated Node joinaliasvars = 14 [json_name="joinaliasvars"]; + repeated Node joinleftcols = 15 [json_name="joinleftcols"]; + repeated Node joinrightcols = 16 [json_name="joinrightcols"]; + Alias join_using_alias = 17 [json_name="join_using_alias"]; + repeated Node functions = 18 [json_name="functions"]; + bool funcordinality = 19 [json_name="funcordinality"]; + TableFunc tablefunc = 20 [json_name="tablefunc"]; + repeated Node values_lists = 21 [json_name="values_lists"]; + string ctename = 22 [json_name="ctename"]; + uint32 ctelevelsup = 23 [json_name="ctelevelsup"]; + bool self_reference = 24 [json_name="self_reference"]; + repeated Node coltypes = 25 [json_name="coltypes"]; + repeated Node coltypmods = 26 [json_name="coltypmods"]; + repeated Node colcollations = 27 [json_name="colcollations"]; + string enrname = 28 [json_name="enrname"]; + double enrtuples = 29 [json_name="enrtuples"]; + bool lateral = 30 [json_name="lateral"]; + bool in_from_cl = 31 [json_name="inFromCl"]; + repeated Node security_quals = 32 [json_name="securityQuals"]; +} + +message RTEPermissionInfo +{ + uint32 relid = 1 [json_name="relid"]; + bool inh = 2 [json_name="inh"]; + uint64 required_perms = 3 [json_name="requiredPerms"]; + uint32 check_as_user = 4 [json_name="checkAsUser"]; + repeated uint64 selected_cols = 5 [json_name="selectedCols"]; + repeated uint64 inserted_cols = 6 [json_name="insertedCols"]; + repeated uint64 updated_cols = 7 [json_name="updatedCols"]; +} + +message RangeTblFunction +{ + Node funcexpr = 1 [json_name="funcexpr"]; + int32 funccolcount = 2 [json_name="funccolcount"]; + repeated Node funccolnames = 3 [json_name="funccolnames"]; + repeated Node funccoltypes = 4 [json_name="funccoltypes"]; + repeated Node funccoltypmods = 5 [json_name="funccoltypmods"]; + repeated Node funccolcollations = 6 [json_name="funccolcollations"]; + repeated uint64 funcparams = 7 [json_name="funcparams"]; +} + +message TableSampleClause +{ + uint32 tsmhandler = 1 [json_name="tsmhandler"]; + repeated Node args = 2 [json_name="args"]; + Node repeatable = 3 [json_name="repeatable"]; +} + +message WithCheckOption +{ + WCOKind kind = 1 [json_name="kind"]; + string relname = 2 [json_name="relname"]; + string polname = 3 [json_name="polname"]; + Node qual = 4 [json_name="qual"]; + bool cascaded = 5 [json_name="cascaded"]; +} + +message SortGroupClause +{ + uint32 tle_sort_group_ref = 1 [json_name="tleSortGroupRef"]; + uint32 eqop = 2 [json_name="eqop"]; + uint32 sortop = 3 [json_name="sortop"]; + bool nulls_first = 4 [json_name="nulls_first"]; + bool hashable = 5 [json_name="hashable"]; +} + +message GroupingSet +{ + GroupingSetKind kind = 1 [json_name="kind"]; + repeated Node content = 2 [json_name="content"]; + int32 location = 3 [json_name="location"]; +} + +message WindowClause +{ + string name = 1 [json_name="name"]; + string refname = 2 [json_name="refname"]; + repeated Node partition_clause = 3 [json_name="partitionClause"]; + repeated Node order_clause = 4 [json_name="orderClause"]; + int32 frame_options = 5 [json_name="frameOptions"]; + Node start_offset = 6 [json_name="startOffset"]; + Node end_offset = 7 [json_name="endOffset"]; + uint32 start_in_range_func = 8 [json_name="startInRangeFunc"]; + uint32 end_in_range_func = 9 [json_name="endInRangeFunc"]; + uint32 in_range_coll = 10 [json_name="inRangeColl"]; + bool in_range_asc = 11 [json_name="inRangeAsc"]; + bool in_range_nulls_first = 12 [json_name="inRangeNullsFirst"]; + uint32 winref = 13 [json_name="winref"]; + bool copied_order = 14 [json_name="copiedOrder"]; +} + +message RowMarkClause +{ + uint32 rti = 1 [json_name="rti"]; + LockClauseStrength strength = 2 [json_name="strength"]; + LockWaitPolicy wait_policy = 3 [json_name="waitPolicy"]; + bool pushed_down = 4 [json_name="pushedDown"]; +} + +message WithClause +{ + repeated Node ctes = 1 [json_name="ctes"]; + bool recursive = 2 [json_name="recursive"]; + int32 location = 3 [json_name="location"]; +} + +message InferClause +{ + repeated Node index_elems = 1 [json_name="indexElems"]; + Node where_clause = 2 [json_name="whereClause"]; + string conname = 3 [json_name="conname"]; + int32 location = 4 [json_name="location"]; +} + +message OnConflictClause +{ + OnConflictAction action = 1 [json_name="action"]; + InferClause infer = 2 [json_name="infer"]; + repeated Node target_list = 3 [json_name="targetList"]; + Node where_clause = 4 [json_name="whereClause"]; + int32 location = 5 [json_name="location"]; +} + +message CTESearchClause +{ + repeated Node search_col_list = 1 [json_name="search_col_list"]; + bool search_breadth_first = 2 [json_name="search_breadth_first"]; + string search_seq_column = 3 [json_name="search_seq_column"]; + int32 location = 4 [json_name="location"]; +} + +message CTECycleClause +{ + repeated Node cycle_col_list = 1 [json_name="cycle_col_list"]; + string cycle_mark_column = 2 [json_name="cycle_mark_column"]; + Node cycle_mark_value = 3 [json_name="cycle_mark_value"]; + Node cycle_mark_default = 4 [json_name="cycle_mark_default"]; + string cycle_path_column = 5 [json_name="cycle_path_column"]; + int32 location = 6 [json_name="location"]; + uint32 cycle_mark_type = 7 [json_name="cycle_mark_type"]; + int32 cycle_mark_typmod = 8 [json_name="cycle_mark_typmod"]; + uint32 cycle_mark_collation = 9 [json_name="cycle_mark_collation"]; + uint32 cycle_mark_neop = 10 [json_name="cycle_mark_neop"]; +} + +message CommonTableExpr +{ + string ctename = 1 [json_name="ctename"]; + repeated Node aliascolnames = 2 [json_name="aliascolnames"]; + CTEMaterialize ctematerialized = 3 [json_name="ctematerialized"]; + Node ctequery = 4 [json_name="ctequery"]; + CTESearchClause search_clause = 5 [json_name="search_clause"]; + CTECycleClause cycle_clause = 6 [json_name="cycle_clause"]; + int32 location = 7 [json_name="location"]; + bool cterecursive = 8 [json_name="cterecursive"]; + int32 cterefcount = 9 [json_name="cterefcount"]; + repeated Node ctecolnames = 10 [json_name="ctecolnames"]; + repeated Node ctecoltypes = 11 [json_name="ctecoltypes"]; + repeated Node ctecoltypmods = 12 [json_name="ctecoltypmods"]; + repeated Node ctecolcollations = 13 [json_name="ctecolcollations"]; +} + +message MergeWhenClause +{ + MergeMatchKind match_kind = 1 [json_name="matchKind"]; + CmdType command_type = 2 [json_name="commandType"]; + OverridingKind override = 3 [json_name="override"]; + Node condition = 4 [json_name="condition"]; + repeated Node target_list = 5 [json_name="targetList"]; + repeated Node values = 6 [json_name="values"]; +} + +message TriggerTransition +{ + string name = 1 [json_name="name"]; + bool is_new = 2 [json_name="isNew"]; + bool is_table = 3 [json_name="isTable"]; +} + +message JsonOutput +{ + TypeName type_name = 1 [json_name="typeName"]; + JsonReturning returning = 2 [json_name="returning"]; +} + +message JsonArgument +{ + JsonValueExpr val = 1 [json_name="val"]; + string name = 2 [json_name="name"]; +} + +message JsonFuncExpr +{ + JsonExprOp op = 1 [json_name="op"]; + string column_name = 2 [json_name="column_name"]; + JsonValueExpr context_item = 3 [json_name="context_item"]; + Node pathspec = 4 [json_name="pathspec"]; + repeated Node passing = 5 [json_name="passing"]; + JsonOutput output = 6 [json_name="output"]; + JsonBehavior on_empty = 7 [json_name="on_empty"]; + JsonBehavior on_error = 8 [json_name="on_error"]; + JsonWrapper wrapper = 9 [json_name="wrapper"]; + JsonQuotes quotes = 10 [json_name="quotes"]; + int32 location = 11 [json_name="location"]; +} + +message JsonTablePathSpec +{ + Node string = 1 [json_name="string"]; + string name = 2 [json_name="name"]; + int32 name_location = 3 [json_name="name_location"]; + int32 location = 4 [json_name="location"]; +} + +message JsonTable +{ + JsonValueExpr context_item = 1 [json_name="context_item"]; + JsonTablePathSpec pathspec = 2 [json_name="pathspec"]; + repeated Node passing = 3 [json_name="passing"]; + repeated Node columns = 4 [json_name="columns"]; + JsonBehavior on_error = 5 [json_name="on_error"]; + Alias alias = 6 [json_name="alias"]; + bool lateral = 7 [json_name="lateral"]; + int32 location = 8 [json_name="location"]; +} + +message JsonTableColumn +{ + JsonTableColumnType coltype = 1 [json_name="coltype"]; + string name = 2 [json_name="name"]; + TypeName type_name = 3 [json_name="typeName"]; + JsonTablePathSpec pathspec = 4 [json_name="pathspec"]; + JsonFormat format = 5 [json_name="format"]; + JsonWrapper wrapper = 6 [json_name="wrapper"]; + JsonQuotes quotes = 7 [json_name="quotes"]; + repeated Node columns = 8 [json_name="columns"]; + JsonBehavior on_empty = 9 [json_name="on_empty"]; + JsonBehavior on_error = 10 [json_name="on_error"]; + int32 location = 11 [json_name="location"]; +} + +message JsonKeyValue +{ + Node key = 1 [json_name="key"]; + JsonValueExpr value = 2 [json_name="value"]; +} + +message JsonParseExpr +{ + JsonValueExpr expr = 1 [json_name="expr"]; + JsonOutput output = 2 [json_name="output"]; + bool unique_keys = 3 [json_name="unique_keys"]; + int32 location = 4 [json_name="location"]; +} + +message JsonScalarExpr +{ + Node expr = 1 [json_name="expr"]; + JsonOutput output = 2 [json_name="output"]; + int32 location = 3 [json_name="location"]; +} + +message JsonSerializeExpr +{ + JsonValueExpr expr = 1 [json_name="expr"]; + JsonOutput output = 2 [json_name="output"]; + int32 location = 3 [json_name="location"]; +} + +message JsonObjectConstructor +{ + repeated Node exprs = 1 [json_name="exprs"]; + JsonOutput output = 2 [json_name="output"]; + bool absent_on_null = 3 [json_name="absent_on_null"]; + bool unique = 4 [json_name="unique"]; + int32 location = 5 [json_name="location"]; +} + +message JsonArrayConstructor +{ + repeated Node exprs = 1 [json_name="exprs"]; + JsonOutput output = 2 [json_name="output"]; + bool absent_on_null = 3 [json_name="absent_on_null"]; + int32 location = 4 [json_name="location"]; +} + +message JsonArrayQueryConstructor +{ + Node query = 1 [json_name="query"]; + JsonOutput output = 2 [json_name="output"]; + JsonFormat format = 3 [json_name="format"]; + bool absent_on_null = 4 [json_name="absent_on_null"]; + int32 location = 5 [json_name="location"]; +} + +message JsonAggConstructor +{ + JsonOutput output = 1 [json_name="output"]; + Node agg_filter = 2 [json_name="agg_filter"]; + repeated Node agg_order = 3 [json_name="agg_order"]; + WindowDef over = 4 [json_name="over"]; + int32 location = 5 [json_name="location"]; +} + +message JsonObjectAgg +{ + JsonAggConstructor constructor = 1 [json_name="constructor"]; + JsonKeyValue arg = 2 [json_name="arg"]; + bool absent_on_null = 3 [json_name="absent_on_null"]; + bool unique = 4 [json_name="unique"]; +} + +message JsonArrayAgg +{ + JsonAggConstructor constructor = 1 [json_name="constructor"]; + JsonValueExpr arg = 2 [json_name="arg"]; + bool absent_on_null = 3 [json_name="absent_on_null"]; +} + +message RawStmt +{ + Node stmt = 1 [json_name="stmt"]; + int32 stmt_location = 2 [json_name="stmt_location"]; + int32 stmt_len = 3 [json_name="stmt_len"]; +} + +message InsertStmt +{ + RangeVar relation = 1 [json_name="relation"]; + repeated Node cols = 2 [json_name="cols"]; + Node select_stmt = 3 [json_name="selectStmt"]; + OnConflictClause on_conflict_clause = 4 [json_name="onConflictClause"]; + repeated Node returning_list = 5 [json_name="returningList"]; + WithClause with_clause = 6 [json_name="withClause"]; + OverridingKind override = 7 [json_name="override"]; +} + +message DeleteStmt +{ + RangeVar relation = 1 [json_name="relation"]; + repeated Node using_clause = 2 [json_name="usingClause"]; + Node where_clause = 3 [json_name="whereClause"]; + repeated Node returning_list = 4 [json_name="returningList"]; + WithClause with_clause = 5 [json_name="withClause"]; +} + +message UpdateStmt +{ + RangeVar relation = 1 [json_name="relation"]; + repeated Node target_list = 2 [json_name="targetList"]; + Node where_clause = 3 [json_name="whereClause"]; + repeated Node from_clause = 4 [json_name="fromClause"]; + repeated Node returning_list = 5 [json_name="returningList"]; + WithClause with_clause = 6 [json_name="withClause"]; +} + +message MergeStmt +{ + RangeVar relation = 1 [json_name="relation"]; + Node source_relation = 2 [json_name="sourceRelation"]; + Node join_condition = 3 [json_name="joinCondition"]; + repeated Node merge_when_clauses = 4 [json_name="mergeWhenClauses"]; + repeated Node returning_list = 5 [json_name="returningList"]; + WithClause with_clause = 6 [json_name="withClause"]; +} + +message SelectStmt +{ + repeated Node distinct_clause = 1 [json_name="distinctClause"]; + IntoClause into_clause = 2 [json_name="intoClause"]; + repeated Node target_list = 3 [json_name="targetList"]; + repeated Node from_clause = 4 [json_name="fromClause"]; + Node where_clause = 5 [json_name="whereClause"]; + repeated Node group_clause = 6 [json_name="groupClause"]; + bool group_distinct = 7 [json_name="groupDistinct"]; + Node having_clause = 8 [json_name="havingClause"]; + repeated Node window_clause = 9 [json_name="windowClause"]; + repeated Node values_lists = 10 [json_name="valuesLists"]; + repeated Node sort_clause = 11 [json_name="sortClause"]; + Node limit_offset = 12 [json_name="limitOffset"]; + Node limit_count = 13 [json_name="limitCount"]; + LimitOption limit_option = 14 [json_name="limitOption"]; + repeated Node locking_clause = 15 [json_name="lockingClause"]; + WithClause with_clause = 16 [json_name="withClause"]; + SetOperation op = 17 [json_name="op"]; + bool all = 18 [json_name="all"]; + SelectStmt larg = 19 [json_name="larg"]; + SelectStmt rarg = 20 [json_name="rarg"]; +} + +message SetOperationStmt +{ + SetOperation op = 1 [json_name="op"]; + bool all = 2 [json_name="all"]; + Node larg = 3 [json_name="larg"]; + Node rarg = 4 [json_name="rarg"]; + repeated Node col_types = 5 [json_name="colTypes"]; + repeated Node col_typmods = 6 [json_name="colTypmods"]; + repeated Node col_collations = 7 [json_name="colCollations"]; + repeated Node group_clauses = 8 [json_name="groupClauses"]; +} + +message ReturnStmt +{ + Node returnval = 1 [json_name="returnval"]; +} + +message PLAssignStmt +{ + string name = 1 [json_name="name"]; + repeated Node indirection = 2 [json_name="indirection"]; + int32 nnames = 3 [json_name="nnames"]; + SelectStmt val = 4 [json_name="val"]; + int32 location = 5 [json_name="location"]; +} + +message CreateSchemaStmt +{ + string schemaname = 1 [json_name="schemaname"]; + RoleSpec authrole = 2 [json_name="authrole"]; + repeated Node schema_elts = 3 [json_name="schemaElts"]; + bool if_not_exists = 4 [json_name="if_not_exists"]; +} + +message AlterTableStmt +{ + RangeVar relation = 1 [json_name="relation"]; + repeated Node cmds = 2 [json_name="cmds"]; + ObjectType objtype = 3 [json_name="objtype"]; + bool missing_ok = 4 [json_name="missing_ok"]; +} + +message ReplicaIdentityStmt +{ + string identity_type = 1 [json_name="identity_type"]; + string name = 2 [json_name="name"]; +} + +message AlterTableCmd +{ + AlterTableType subtype = 1 [json_name="subtype"]; + string name = 2 [json_name="name"]; + int32 num = 3 [json_name="num"]; + RoleSpec newowner = 4 [json_name="newowner"]; + Node def = 5 [json_name="def"]; + DropBehavior behavior = 6 [json_name="behavior"]; + bool missing_ok = 7 [json_name="missing_ok"]; + bool recurse = 8 [json_name="recurse"]; +} + +message AlterCollationStmt +{ + repeated Node collname = 1 [json_name="collname"]; +} + +message AlterDomainStmt +{ + string subtype = 1 [json_name="subtype"]; + repeated Node type_name = 2 [json_name="typeName"]; + string name = 3 [json_name="name"]; + Node def = 4 [json_name="def"]; + DropBehavior behavior = 5 [json_name="behavior"]; + bool missing_ok = 6 [json_name="missing_ok"]; +} + +message GrantStmt +{ + bool is_grant = 1 [json_name="is_grant"]; + GrantTargetType targtype = 2 [json_name="targtype"]; + ObjectType objtype = 3 [json_name="objtype"]; + repeated Node objects = 4 [json_name="objects"]; + repeated Node privileges = 5 [json_name="privileges"]; + repeated Node grantees = 6 [json_name="grantees"]; + bool grant_option = 7 [json_name="grant_option"]; + RoleSpec grantor = 8 [json_name="grantor"]; + DropBehavior behavior = 9 [json_name="behavior"]; +} + +message ObjectWithArgs +{ + repeated Node objname = 1 [json_name="objname"]; + repeated Node objargs = 2 [json_name="objargs"]; + repeated Node objfuncargs = 3 [json_name="objfuncargs"]; + bool args_unspecified = 4 [json_name="args_unspecified"]; +} + +message AccessPriv +{ + string priv_name = 1 [json_name="priv_name"]; + repeated Node cols = 2 [json_name="cols"]; +} + +message GrantRoleStmt +{ + repeated Node granted_roles = 1 [json_name="granted_roles"]; + repeated Node grantee_roles = 2 [json_name="grantee_roles"]; + bool is_grant = 3 [json_name="is_grant"]; + repeated Node opt = 4 [json_name="opt"]; + RoleSpec grantor = 5 [json_name="grantor"]; + DropBehavior behavior = 6 [json_name="behavior"]; +} + +message AlterDefaultPrivilegesStmt +{ + repeated Node options = 1 [json_name="options"]; + GrantStmt action = 2 [json_name="action"]; +} + +message CopyStmt +{ + RangeVar relation = 1 [json_name="relation"]; + Node query = 2 [json_name="query"]; + repeated Node attlist = 3 [json_name="attlist"]; + bool is_from = 4 [json_name="is_from"]; + bool is_program = 5 [json_name="is_program"]; + string filename = 6 [json_name="filename"]; + repeated Node options = 7 [json_name="options"]; + Node where_clause = 8 [json_name="whereClause"]; +} + +message VariableSetStmt +{ + VariableSetKind kind = 1 [json_name="kind"]; + string name = 2 [json_name="name"]; + repeated Node args = 3 [json_name="args"]; + bool is_local = 4 [json_name="is_local"]; +} + +message VariableShowStmt +{ + string name = 1 [json_name="name"]; +} + +message CreateStmt +{ + RangeVar relation = 1 [json_name="relation"]; + repeated Node table_elts = 2 [json_name="tableElts"]; + repeated Node inh_relations = 3 [json_name="inhRelations"]; + PartitionBoundSpec partbound = 4 [json_name="partbound"]; + PartitionSpec partspec = 5 [json_name="partspec"]; + TypeName of_typename = 6 [json_name="ofTypename"]; + repeated Node constraints = 7 [json_name="constraints"]; + repeated Node options = 8 [json_name="options"]; + OnCommitAction oncommit = 9 [json_name="oncommit"]; + string tablespacename = 10 [json_name="tablespacename"]; + string access_method = 11 [json_name="accessMethod"]; + bool if_not_exists = 12 [json_name="if_not_exists"]; +} + +message Constraint +{ + ConstrType contype = 1 [json_name="contype"]; + string conname = 2 [json_name="conname"]; + bool deferrable = 3 [json_name="deferrable"]; + bool initdeferred = 4 [json_name="initdeferred"]; + bool skip_validation = 5 [json_name="skip_validation"]; + bool initially_valid = 6 [json_name="initially_valid"]; + bool is_no_inherit = 7 [json_name="is_no_inherit"]; + Node raw_expr = 8 [json_name="raw_expr"]; + string cooked_expr = 9 [json_name="cooked_expr"]; + string generated_when = 10 [json_name="generated_when"]; + int32 inhcount = 11 [json_name="inhcount"]; + bool nulls_not_distinct = 12 [json_name="nulls_not_distinct"]; + repeated Node keys = 13 [json_name="keys"]; + repeated Node including = 14 [json_name="including"]; + repeated Node exclusions = 15 [json_name="exclusions"]; + repeated Node options = 16 [json_name="options"]; + string indexname = 17 [json_name="indexname"]; + string indexspace = 18 [json_name="indexspace"]; + bool reset_default_tblspc = 19 [json_name="reset_default_tblspc"]; + string access_method = 20 [json_name="access_method"]; + Node where_clause = 21 [json_name="where_clause"]; + RangeVar pktable = 22 [json_name="pktable"]; + repeated Node fk_attrs = 23 [json_name="fk_attrs"]; + repeated Node pk_attrs = 24 [json_name="pk_attrs"]; + string fk_matchtype = 25 [json_name="fk_matchtype"]; + string fk_upd_action = 26 [json_name="fk_upd_action"]; + string fk_del_action = 27 [json_name="fk_del_action"]; + repeated Node fk_del_set_cols = 28 [json_name="fk_del_set_cols"]; + repeated Node old_conpfeqop = 29 [json_name="old_conpfeqop"]; + uint32 old_pktable_oid = 30 [json_name="old_pktable_oid"]; + int32 location = 31 [json_name="location"]; +} + +message CreateTableSpaceStmt +{ + string tablespacename = 1 [json_name="tablespacename"]; + RoleSpec owner = 2 [json_name="owner"]; + string location = 3 [json_name="location"]; + repeated Node options = 4 [json_name="options"]; +} + +message DropTableSpaceStmt +{ + string tablespacename = 1 [json_name="tablespacename"]; + bool missing_ok = 2 [json_name="missing_ok"]; +} + +message AlterTableSpaceOptionsStmt +{ + string tablespacename = 1 [json_name="tablespacename"]; + repeated Node options = 2 [json_name="options"]; + bool is_reset = 3 [json_name="isReset"]; +} + +message AlterTableMoveAllStmt +{ + string orig_tablespacename = 1 [json_name="orig_tablespacename"]; + ObjectType objtype = 2 [json_name="objtype"]; + repeated Node roles = 3 [json_name="roles"]; + string new_tablespacename = 4 [json_name="new_tablespacename"]; + bool nowait = 5 [json_name="nowait"]; +} + +message CreateExtensionStmt +{ + string extname = 1 [json_name="extname"]; + bool if_not_exists = 2 [json_name="if_not_exists"]; + repeated Node options = 3 [json_name="options"]; +} + +message AlterExtensionStmt +{ + string extname = 1 [json_name="extname"]; + repeated Node options = 2 [json_name="options"]; +} + +message AlterExtensionContentsStmt +{ + string extname = 1 [json_name="extname"]; + int32 action = 2 [json_name="action"]; + ObjectType objtype = 3 [json_name="objtype"]; + Node object = 4 [json_name="object"]; +} + +message CreateFdwStmt +{ + string fdwname = 1 [json_name="fdwname"]; + repeated Node func_options = 2 [json_name="func_options"]; + repeated Node options = 3 [json_name="options"]; +} + +message AlterFdwStmt +{ + string fdwname = 1 [json_name="fdwname"]; + repeated Node func_options = 2 [json_name="func_options"]; + repeated Node options = 3 [json_name="options"]; +} + +message CreateForeignServerStmt +{ + string servername = 1 [json_name="servername"]; + string servertype = 2 [json_name="servertype"]; + string version = 3 [json_name="version"]; + string fdwname = 4 [json_name="fdwname"]; + bool if_not_exists = 5 [json_name="if_not_exists"]; + repeated Node options = 6 [json_name="options"]; +} + +message AlterForeignServerStmt +{ + string servername = 1 [json_name="servername"]; + string version = 2 [json_name="version"]; + repeated Node options = 3 [json_name="options"]; + bool has_version = 4 [json_name="has_version"]; +} + +message CreateForeignTableStmt +{ + CreateStmt base_stmt = 1 [json_name="base"]; + string servername = 2 [json_name="servername"]; + repeated Node options = 3 [json_name="options"]; +} + +message CreateUserMappingStmt +{ + RoleSpec user = 1 [json_name="user"]; + string servername = 2 [json_name="servername"]; + bool if_not_exists = 3 [json_name="if_not_exists"]; + repeated Node options = 4 [json_name="options"]; +} + +message AlterUserMappingStmt +{ + RoleSpec user = 1 [json_name="user"]; + string servername = 2 [json_name="servername"]; + repeated Node options = 3 [json_name="options"]; +} + +message DropUserMappingStmt +{ + RoleSpec user = 1 [json_name="user"]; + string servername = 2 [json_name="servername"]; + bool missing_ok = 3 [json_name="missing_ok"]; +} + +message ImportForeignSchemaStmt +{ + string server_name = 1 [json_name="server_name"]; + string remote_schema = 2 [json_name="remote_schema"]; + string local_schema = 3 [json_name="local_schema"]; + ImportForeignSchemaType list_type = 4 [json_name="list_type"]; + repeated Node table_list = 5 [json_name="table_list"]; + repeated Node options = 6 [json_name="options"]; +} + +message CreatePolicyStmt +{ + string policy_name = 1 [json_name="policy_name"]; + RangeVar table = 2 [json_name="table"]; + string cmd_name = 3 [json_name="cmd_name"]; + bool permissive = 4 [json_name="permissive"]; + repeated Node roles = 5 [json_name="roles"]; + Node qual = 6 [json_name="qual"]; + Node with_check = 7 [json_name="with_check"]; +} + +message AlterPolicyStmt +{ + string policy_name = 1 [json_name="policy_name"]; + RangeVar table = 2 [json_name="table"]; + repeated Node roles = 3 [json_name="roles"]; + Node qual = 4 [json_name="qual"]; + Node with_check = 5 [json_name="with_check"]; +} + +message CreateAmStmt +{ + string amname = 1 [json_name="amname"]; + repeated Node handler_name = 2 [json_name="handler_name"]; + string amtype = 3 [json_name="amtype"]; +} + +message CreateTrigStmt +{ + bool replace = 1 [json_name="replace"]; + bool isconstraint = 2 [json_name="isconstraint"]; + string trigname = 3 [json_name="trigname"]; + RangeVar relation = 4 [json_name="relation"]; + repeated Node funcname = 5 [json_name="funcname"]; + repeated Node args = 6 [json_name="args"]; + bool row = 7 [json_name="row"]; + int32 timing = 8 [json_name="timing"]; + int32 events = 9 [json_name="events"]; + repeated Node columns = 10 [json_name="columns"]; + Node when_clause = 11 [json_name="whenClause"]; + repeated Node transition_rels = 12 [json_name="transitionRels"]; + bool deferrable = 13 [json_name="deferrable"]; + bool initdeferred = 14 [json_name="initdeferred"]; + RangeVar constrrel = 15 [json_name="constrrel"]; +} + +message CreateEventTrigStmt +{ + string trigname = 1 [json_name="trigname"]; + string eventname = 2 [json_name="eventname"]; + repeated Node whenclause = 3 [json_name="whenclause"]; + repeated Node funcname = 4 [json_name="funcname"]; +} + +message AlterEventTrigStmt +{ + string trigname = 1 [json_name="trigname"]; + string tgenabled = 2 [json_name="tgenabled"]; +} + +message CreatePLangStmt +{ + bool replace = 1 [json_name="replace"]; + string plname = 2 [json_name="plname"]; + repeated Node plhandler = 3 [json_name="plhandler"]; + repeated Node plinline = 4 [json_name="plinline"]; + repeated Node plvalidator = 5 [json_name="plvalidator"]; + bool pltrusted = 6 [json_name="pltrusted"]; +} + +message CreateRoleStmt +{ + RoleStmtType stmt_type = 1 [json_name="stmt_type"]; + string role = 2 [json_name="role"]; + repeated Node options = 3 [json_name="options"]; +} + +message AlterRoleStmt +{ + RoleSpec role = 1 [json_name="role"]; + repeated Node options = 2 [json_name="options"]; + int32 action = 3 [json_name="action"]; +} + +message AlterRoleSetStmt +{ + RoleSpec role = 1 [json_name="role"]; + string database = 2 [json_name="database"]; + VariableSetStmt setstmt = 3 [json_name="setstmt"]; +} + +message DropRoleStmt +{ + repeated Node roles = 1 [json_name="roles"]; + bool missing_ok = 2 [json_name="missing_ok"]; +} + +message CreateSeqStmt +{ + RangeVar sequence = 1 [json_name="sequence"]; + repeated Node options = 2 [json_name="options"]; + uint32 owner_id = 3 [json_name="ownerId"]; + bool for_identity = 4 [json_name="for_identity"]; + bool if_not_exists = 5 [json_name="if_not_exists"]; +} + +message AlterSeqStmt +{ + RangeVar sequence = 1 [json_name="sequence"]; + repeated Node options = 2 [json_name="options"]; + bool for_identity = 3 [json_name="for_identity"]; + bool missing_ok = 4 [json_name="missing_ok"]; +} + +message DefineStmt +{ + ObjectType kind = 1 [json_name="kind"]; + bool oldstyle = 2 [json_name="oldstyle"]; + repeated Node defnames = 3 [json_name="defnames"]; + repeated Node args = 4 [json_name="args"]; + repeated Node definition = 5 [json_name="definition"]; + bool if_not_exists = 6 [json_name="if_not_exists"]; + bool replace = 7 [json_name="replace"]; +} + +message CreateDomainStmt +{ + repeated Node domainname = 1 [json_name="domainname"]; + TypeName type_name = 2 [json_name="typeName"]; + CollateClause coll_clause = 3 [json_name="collClause"]; + repeated Node constraints = 4 [json_name="constraints"]; +} + +message CreateOpClassStmt +{ + repeated Node opclassname = 1 [json_name="opclassname"]; + repeated Node opfamilyname = 2 [json_name="opfamilyname"]; + string amname = 3 [json_name="amname"]; + TypeName datatype = 4 [json_name="datatype"]; + repeated Node items = 5 [json_name="items"]; + bool is_default = 6 [json_name="isDefault"]; +} + +message CreateOpClassItem +{ + int32 itemtype = 1 [json_name="itemtype"]; + ObjectWithArgs name = 2 [json_name="name"]; + int32 number = 3 [json_name="number"]; + repeated Node order_family = 4 [json_name="order_family"]; + repeated Node class_args = 5 [json_name="class_args"]; + TypeName storedtype = 6 [json_name="storedtype"]; +} + +message CreateOpFamilyStmt +{ + repeated Node opfamilyname = 1 [json_name="opfamilyname"]; + string amname = 2 [json_name="amname"]; +} + +message AlterOpFamilyStmt +{ + repeated Node opfamilyname = 1 [json_name="opfamilyname"]; + string amname = 2 [json_name="amname"]; + bool is_drop = 3 [json_name="isDrop"]; + repeated Node items = 4 [json_name="items"]; +} + +message DropStmt +{ + repeated Node objects = 1 [json_name="objects"]; + ObjectType remove_type = 2 [json_name="removeType"]; + DropBehavior behavior = 3 [json_name="behavior"]; + bool missing_ok = 4 [json_name="missing_ok"]; + bool concurrent = 5 [json_name="concurrent"]; +} + +message TruncateStmt +{ + repeated Node relations = 1 [json_name="relations"]; + bool restart_seqs = 2 [json_name="restart_seqs"]; + DropBehavior behavior = 3 [json_name="behavior"]; +} + +message CommentStmt +{ + ObjectType objtype = 1 [json_name="objtype"]; + Node object = 2 [json_name="object"]; + string comment = 3 [json_name="comment"]; +} + +message SecLabelStmt +{ + ObjectType objtype = 1 [json_name="objtype"]; + Node object = 2 [json_name="object"]; + string provider = 3 [json_name="provider"]; + string label = 4 [json_name="label"]; +} + +message DeclareCursorStmt +{ + string portalname = 1 [json_name="portalname"]; + int32 options = 2 [json_name="options"]; + Node query = 3 [json_name="query"]; +} + +message ClosePortalStmt +{ + string portalname = 1 [json_name="portalname"]; +} + +message FetchStmt +{ + FetchDirection direction = 1 [json_name="direction"]; + int64 how_many = 2 [json_name="howMany"]; + string portalname = 3 [json_name="portalname"]; + bool ismove = 4 [json_name="ismove"]; +} + +message IndexStmt +{ + string idxname = 1 [json_name="idxname"]; + RangeVar relation = 2 [json_name="relation"]; + string access_method = 3 [json_name="accessMethod"]; + string table_space = 4 [json_name="tableSpace"]; + repeated Node index_params = 5 [json_name="indexParams"]; + repeated Node index_including_params = 6 [json_name="indexIncludingParams"]; + repeated Node options = 7 [json_name="options"]; + Node where_clause = 8 [json_name="whereClause"]; + repeated Node exclude_op_names = 9 [json_name="excludeOpNames"]; + string idxcomment = 10 [json_name="idxcomment"]; + uint32 index_oid = 11 [json_name="indexOid"]; + uint32 old_number = 12 [json_name="oldNumber"]; + uint32 old_create_subid = 13 [json_name="oldCreateSubid"]; + uint32 old_first_relfilelocator_subid = 14 [json_name="oldFirstRelfilelocatorSubid"]; + bool unique = 15 [json_name="unique"]; + bool nulls_not_distinct = 16 [json_name="nulls_not_distinct"]; + bool primary = 17 [json_name="primary"]; + bool isconstraint = 18 [json_name="isconstraint"]; + bool deferrable = 19 [json_name="deferrable"]; + bool initdeferred = 20 [json_name="initdeferred"]; + bool transformed = 21 [json_name="transformed"]; + bool concurrent = 22 [json_name="concurrent"]; + bool if_not_exists = 23 [json_name="if_not_exists"]; + bool reset_default_tblspc = 24 [json_name="reset_default_tblspc"]; +} + +message CreateStatsStmt +{ + repeated Node defnames = 1 [json_name="defnames"]; + repeated Node stat_types = 2 [json_name="stat_types"]; + repeated Node exprs = 3 [json_name="exprs"]; + repeated Node relations = 4 [json_name="relations"]; + string stxcomment = 5 [json_name="stxcomment"]; + bool transformed = 6 [json_name="transformed"]; + bool if_not_exists = 7 [json_name="if_not_exists"]; +} + +message StatsElem +{ + string name = 1 [json_name="name"]; + Node expr = 2 [json_name="expr"]; +} + +message AlterStatsStmt +{ + repeated Node defnames = 1 [json_name="defnames"]; + Node stxstattarget = 2 [json_name="stxstattarget"]; + bool missing_ok = 3 [json_name="missing_ok"]; +} + +message CreateFunctionStmt +{ + bool is_procedure = 1 [json_name="is_procedure"]; + bool replace = 2 [json_name="replace"]; + repeated Node funcname = 3 [json_name="funcname"]; + repeated Node parameters = 4 [json_name="parameters"]; + TypeName return_type = 5 [json_name="returnType"]; + repeated Node options = 6 [json_name="options"]; + Node sql_body = 7 [json_name="sql_body"]; +} + +message FunctionParameter +{ + string name = 1 [json_name="name"]; + TypeName arg_type = 2 [json_name="argType"]; + FunctionParameterMode mode = 3 [json_name="mode"]; + Node defexpr = 4 [json_name="defexpr"]; +} + +message AlterFunctionStmt +{ + ObjectType objtype = 1 [json_name="objtype"]; + ObjectWithArgs func = 2 [json_name="func"]; + repeated Node actions = 3 [json_name="actions"]; +} + +message DoStmt +{ + repeated Node args = 1 [json_name="args"]; +} + +message InlineCodeBlock +{ + string source_text = 1 [json_name="source_text"]; + uint32 lang_oid = 2 [json_name="langOid"]; + bool lang_is_trusted = 3 [json_name="langIsTrusted"]; + bool atomic = 4 [json_name="atomic"]; +} + +message CallStmt +{ + FuncCall funccall = 1 [json_name="funccall"]; + FuncExpr funcexpr = 2 [json_name="funcexpr"]; + repeated Node outargs = 3 [json_name="outargs"]; +} + +message CallContext +{ + bool atomic = 1 [json_name="atomic"]; +} + +message RenameStmt +{ + ObjectType rename_type = 1 [json_name="renameType"]; + ObjectType relation_type = 2 [json_name="relationType"]; + RangeVar relation = 3 [json_name="relation"]; + Node object = 4 [json_name="object"]; + string subname = 5 [json_name="subname"]; + string newname = 6 [json_name="newname"]; + DropBehavior behavior = 7 [json_name="behavior"]; + bool missing_ok = 8 [json_name="missing_ok"]; +} + +message AlterObjectDependsStmt +{ + ObjectType object_type = 1 [json_name="objectType"]; + RangeVar relation = 2 [json_name="relation"]; + Node object = 3 [json_name="object"]; + String extname = 4 [json_name="extname"]; + bool remove = 5 [json_name="remove"]; +} + +message AlterObjectSchemaStmt +{ + ObjectType object_type = 1 [json_name="objectType"]; + RangeVar relation = 2 [json_name="relation"]; + Node object = 3 [json_name="object"]; + string newschema = 4 [json_name="newschema"]; + bool missing_ok = 5 [json_name="missing_ok"]; +} + +message AlterOwnerStmt +{ + ObjectType object_type = 1 [json_name="objectType"]; + RangeVar relation = 2 [json_name="relation"]; + Node object = 3 [json_name="object"]; + RoleSpec newowner = 4 [json_name="newowner"]; +} + +message AlterOperatorStmt +{ + ObjectWithArgs opername = 1 [json_name="opername"]; + repeated Node options = 2 [json_name="options"]; +} + +message AlterTypeStmt +{ + repeated Node type_name = 1 [json_name="typeName"]; + repeated Node options = 2 [json_name="options"]; +} + +message RuleStmt +{ + RangeVar relation = 1 [json_name="relation"]; + string rulename = 2 [json_name="rulename"]; + Node where_clause = 3 [json_name="whereClause"]; + CmdType event = 4 [json_name="event"]; + bool instead = 5 [json_name="instead"]; + repeated Node actions = 6 [json_name="actions"]; + bool replace = 7 [json_name="replace"]; +} + +message NotifyStmt +{ + string conditionname = 1 [json_name="conditionname"]; + string payload = 2 [json_name="payload"]; +} + +message ListenStmt +{ + string conditionname = 1 [json_name="conditionname"]; +} + +message UnlistenStmt +{ + string conditionname = 1 [json_name="conditionname"]; +} + +message TransactionStmt +{ + TransactionStmtKind kind = 1 [json_name="kind"]; + repeated Node options = 2 [json_name="options"]; + string savepoint_name = 3 [json_name="savepoint_name"]; + string gid = 4 [json_name="gid"]; + bool chain = 5 [json_name="chain"]; + int32 location = 6 [json_name="location"]; +} + +message CompositeTypeStmt +{ + RangeVar typevar = 1 [json_name="typevar"]; + repeated Node coldeflist = 2 [json_name="coldeflist"]; +} + +message CreateEnumStmt +{ + repeated Node type_name = 1 [json_name="typeName"]; + repeated Node vals = 2 [json_name="vals"]; +} + +message CreateRangeStmt +{ + repeated Node type_name = 1 [json_name="typeName"]; + repeated Node params = 2 [json_name="params"]; +} + +message AlterEnumStmt +{ + repeated Node type_name = 1 [json_name="typeName"]; + string old_val = 2 [json_name="oldVal"]; + string new_val = 3 [json_name="newVal"]; + string new_val_neighbor = 4 [json_name="newValNeighbor"]; + bool new_val_is_after = 5 [json_name="newValIsAfter"]; + bool skip_if_new_val_exists = 6 [json_name="skipIfNewValExists"]; +} + +message ViewStmt +{ + RangeVar view = 1 [json_name="view"]; + repeated Node aliases = 2 [json_name="aliases"]; + Node query = 3 [json_name="query"]; + bool replace = 4 [json_name="replace"]; + repeated Node options = 5 [json_name="options"]; + ViewCheckOption with_check_option = 6 [json_name="withCheckOption"]; +} + +message LoadStmt +{ + string filename = 1 [json_name="filename"]; +} + +message CreatedbStmt +{ + string dbname = 1 [json_name="dbname"]; + repeated Node options = 2 [json_name="options"]; +} + +message AlterDatabaseStmt +{ + string dbname = 1 [json_name="dbname"]; + repeated Node options = 2 [json_name="options"]; +} + +message AlterDatabaseRefreshCollStmt +{ + string dbname = 1 [json_name="dbname"]; +} + +message AlterDatabaseSetStmt +{ + string dbname = 1 [json_name="dbname"]; + VariableSetStmt setstmt = 2 [json_name="setstmt"]; +} + +message DropdbStmt +{ + string dbname = 1 [json_name="dbname"]; + bool missing_ok = 2 [json_name="missing_ok"]; + repeated Node options = 3 [json_name="options"]; +} + +message AlterSystemStmt +{ + VariableSetStmt setstmt = 1 [json_name="setstmt"]; +} + +message ClusterStmt +{ + RangeVar relation = 1 [json_name="relation"]; + string indexname = 2 [json_name="indexname"]; + repeated Node params = 3 [json_name="params"]; +} + +message VacuumStmt +{ + repeated Node options = 1 [json_name="options"]; + repeated Node rels = 2 [json_name="rels"]; + bool is_vacuumcmd = 3 [json_name="is_vacuumcmd"]; +} + +message VacuumRelation +{ + RangeVar relation = 1 [json_name="relation"]; + uint32 oid = 2 [json_name="oid"]; + repeated Node va_cols = 3 [json_name="va_cols"]; +} + +message ExplainStmt +{ + Node query = 1 [json_name="query"]; + repeated Node options = 2 [json_name="options"]; +} + +message CreateTableAsStmt +{ + Node query = 1 [json_name="query"]; + IntoClause into = 2 [json_name="into"]; + ObjectType objtype = 3 [json_name="objtype"]; + bool is_select_into = 4 [json_name="is_select_into"]; + bool if_not_exists = 5 [json_name="if_not_exists"]; +} + +message RefreshMatViewStmt +{ + bool concurrent = 1 [json_name="concurrent"]; + bool skip_data = 2 [json_name="skipData"]; + RangeVar relation = 3 [json_name="relation"]; +} + +message CheckPointStmt +{ +} + +message DiscardStmt +{ + DiscardMode target = 1 [json_name="target"]; +} + +message LockStmt +{ + repeated Node relations = 1 [json_name="relations"]; + int32 mode = 2 [json_name="mode"]; + bool nowait = 3 [json_name="nowait"]; +} + +message ConstraintsSetStmt +{ + repeated Node constraints = 1 [json_name="constraints"]; + bool deferred = 2 [json_name="deferred"]; +} + +message ReindexStmt +{ + ReindexObjectType kind = 1 [json_name="kind"]; + RangeVar relation = 2 [json_name="relation"]; + string name = 3 [json_name="name"]; + repeated Node params = 4 [json_name="params"]; +} + +message CreateConversionStmt +{ + repeated Node conversion_name = 1 [json_name="conversion_name"]; + string for_encoding_name = 2 [json_name="for_encoding_name"]; + string to_encoding_name = 3 [json_name="to_encoding_name"]; + repeated Node func_name = 4 [json_name="func_name"]; + bool def = 5 [json_name="def"]; +} + +message CreateCastStmt +{ + TypeName sourcetype = 1 [json_name="sourcetype"]; + TypeName targettype = 2 [json_name="targettype"]; + ObjectWithArgs func = 3 [json_name="func"]; + CoercionContext context = 4 [json_name="context"]; + bool inout = 5 [json_name="inout"]; +} + +message CreateTransformStmt +{ + bool replace = 1 [json_name="replace"]; + TypeName type_name = 2 [json_name="type_name"]; + string lang = 3 [json_name="lang"]; + ObjectWithArgs fromsql = 4 [json_name="fromsql"]; + ObjectWithArgs tosql = 5 [json_name="tosql"]; +} + +message PrepareStmt +{ + string name = 1 [json_name="name"]; + repeated Node argtypes = 2 [json_name="argtypes"]; + Node query = 3 [json_name="query"]; +} + +message ExecuteStmt +{ + string name = 1 [json_name="name"]; + repeated Node params = 2 [json_name="params"]; +} + +message DeallocateStmt +{ + string name = 1 [json_name="name"]; + bool isall = 2 [json_name="isall"]; + int32 location = 3 [json_name="location"]; +} + +message DropOwnedStmt +{ + repeated Node roles = 1 [json_name="roles"]; + DropBehavior behavior = 2 [json_name="behavior"]; +} + +message ReassignOwnedStmt +{ + repeated Node roles = 1 [json_name="roles"]; + RoleSpec newrole = 2 [json_name="newrole"]; +} + +message AlterTSDictionaryStmt +{ + repeated Node dictname = 1 [json_name="dictname"]; + repeated Node options = 2 [json_name="options"]; +} + +message AlterTSConfigurationStmt +{ + AlterTSConfigType kind = 1 [json_name="kind"]; + repeated Node cfgname = 2 [json_name="cfgname"]; + repeated Node tokentype = 3 [json_name="tokentype"]; + repeated Node dicts = 4 [json_name="dicts"]; + bool override = 5 [json_name="override"]; + bool replace = 6 [json_name="replace"]; + bool missing_ok = 7 [json_name="missing_ok"]; +} + +message PublicationTable +{ + RangeVar relation = 1 [json_name="relation"]; + Node where_clause = 2 [json_name="whereClause"]; + repeated Node columns = 3 [json_name="columns"]; +} + +message PublicationObjSpec +{ + PublicationObjSpecType pubobjtype = 1 [json_name="pubobjtype"]; + string name = 2 [json_name="name"]; + PublicationTable pubtable = 3 [json_name="pubtable"]; + int32 location = 4 [json_name="location"]; +} + +message CreatePublicationStmt +{ + string pubname = 1 [json_name="pubname"]; + repeated Node options = 2 [json_name="options"]; + repeated Node pubobjects = 3 [json_name="pubobjects"]; + bool for_all_tables = 4 [json_name="for_all_tables"]; +} + +message AlterPublicationStmt +{ + string pubname = 1 [json_name="pubname"]; + repeated Node options = 2 [json_name="options"]; + repeated Node pubobjects = 3 [json_name="pubobjects"]; + bool for_all_tables = 4 [json_name="for_all_tables"]; + AlterPublicationAction action = 5 [json_name="action"]; +} + +message CreateSubscriptionStmt +{ + string subname = 1 [json_name="subname"]; + string conninfo = 2 [json_name="conninfo"]; + repeated Node publication = 3 [json_name="publication"]; + repeated Node options = 4 [json_name="options"]; +} + +message AlterSubscriptionStmt +{ + AlterSubscriptionType kind = 1 [json_name="kind"]; + string subname = 2 [json_name="subname"]; + string conninfo = 3 [json_name="conninfo"]; + repeated Node publication = 4 [json_name="publication"]; + repeated Node options = 5 [json_name="options"]; +} + +message DropSubscriptionStmt +{ + string subname = 1 [json_name="subname"]; + bool missing_ok = 2 [json_name="missing_ok"]; + DropBehavior behavior = 3 [json_name="behavior"]; +} + +enum QuerySource +{ + QUERY_SOURCE_UNDEFINED = 0; + QSRC_ORIGINAL = 1; + QSRC_PARSER = 2; + QSRC_INSTEAD_RULE = 3; + QSRC_QUAL_INSTEAD_RULE = 4; + QSRC_NON_INSTEAD_RULE = 5; +} + +enum SortByDir +{ + SORT_BY_DIR_UNDEFINED = 0; + SORTBY_DEFAULT = 1; + SORTBY_ASC = 2; + SORTBY_DESC = 3; + SORTBY_USING = 4; +} + +enum SortByNulls +{ + SORT_BY_NULLS_UNDEFINED = 0; + SORTBY_NULLS_DEFAULT = 1; + SORTBY_NULLS_FIRST = 2; + SORTBY_NULLS_LAST = 3; +} + +enum SetQuantifier +{ + SET_QUANTIFIER_UNDEFINED = 0; + SET_QUANTIFIER_DEFAULT = 1; + SET_QUANTIFIER_ALL = 2; + SET_QUANTIFIER_DISTINCT = 3; +} + +enum A_Expr_Kind +{ + A_EXPR_KIND_UNDEFINED = 0; + AEXPR_OP = 1; + AEXPR_OP_ANY = 2; + AEXPR_OP_ALL = 3; + AEXPR_DISTINCT = 4; + AEXPR_NOT_DISTINCT = 5; + AEXPR_NULLIF = 6; + AEXPR_IN = 7; + AEXPR_LIKE = 8; + AEXPR_ILIKE = 9; + AEXPR_SIMILAR = 10; + AEXPR_BETWEEN = 11; + AEXPR_NOT_BETWEEN = 12; + AEXPR_BETWEEN_SYM = 13; + AEXPR_NOT_BETWEEN_SYM = 14; +} + +enum RoleSpecType +{ + ROLE_SPEC_TYPE_UNDEFINED = 0; + ROLESPEC_CSTRING = 1; + ROLESPEC_CURRENT_ROLE = 2; + ROLESPEC_CURRENT_USER = 3; + ROLESPEC_SESSION_USER = 4; + ROLESPEC_PUBLIC = 5; +} + +enum TableLikeOption +{ + TABLE_LIKE_OPTION_UNDEFINED = 0; + CREATE_TABLE_LIKE_COMMENTS = 1; + CREATE_TABLE_LIKE_COMPRESSION = 2; + CREATE_TABLE_LIKE_CONSTRAINTS = 3; + CREATE_TABLE_LIKE_DEFAULTS = 4; + CREATE_TABLE_LIKE_GENERATED = 5; + CREATE_TABLE_LIKE_IDENTITY = 6; + CREATE_TABLE_LIKE_INDEXES = 7; + CREATE_TABLE_LIKE_STATISTICS = 8; + CREATE_TABLE_LIKE_STORAGE = 9; + CREATE_TABLE_LIKE_ALL = 10; +} + +enum DefElemAction +{ + DEF_ELEM_ACTION_UNDEFINED = 0; + DEFELEM_UNSPEC = 1; + DEFELEM_SET = 2; + DEFELEM_ADD = 3; + DEFELEM_DROP = 4; +} + +enum PartitionStrategy +{ + PARTITION_STRATEGY_UNDEFINED = 0; + PARTITION_STRATEGY_LIST = 1; + PARTITION_STRATEGY_RANGE = 2; + PARTITION_STRATEGY_HASH = 3; +} + +enum PartitionRangeDatumKind +{ + PARTITION_RANGE_DATUM_KIND_UNDEFINED = 0; + PARTITION_RANGE_DATUM_MINVALUE = 1; + PARTITION_RANGE_DATUM_VALUE = 2; + PARTITION_RANGE_DATUM_MAXVALUE = 3; +} + +enum RTEKind +{ + RTEKIND_UNDEFINED = 0; + RTE_RELATION = 1; + RTE_SUBQUERY = 2; + RTE_JOIN = 3; + RTE_FUNCTION = 4; + RTE_TABLEFUNC = 5; + RTE_VALUES = 6; + RTE_CTE = 7; + RTE_NAMEDTUPLESTORE = 8; + RTE_RESULT = 9; +} + +enum WCOKind +{ + WCOKIND_UNDEFINED = 0; + WCO_VIEW_CHECK = 1; + WCO_RLS_INSERT_CHECK = 2; + WCO_RLS_UPDATE_CHECK = 3; + WCO_RLS_CONFLICT_CHECK = 4; + WCO_RLS_MERGE_UPDATE_CHECK = 5; + WCO_RLS_MERGE_DELETE_CHECK = 6; +} + +enum GroupingSetKind +{ + GROUPING_SET_KIND_UNDEFINED = 0; + GROUPING_SET_EMPTY = 1; + GROUPING_SET_SIMPLE = 2; + GROUPING_SET_ROLLUP = 3; + GROUPING_SET_CUBE = 4; + GROUPING_SET_SETS = 5; +} + +enum CTEMaterialize +{ + CTEMATERIALIZE_UNDEFINED = 0; + CTEMaterializeDefault = 1; + CTEMaterializeAlways = 2; + CTEMaterializeNever = 3; +} + +enum JsonQuotes +{ + JSON_QUOTES_UNDEFINED = 0; + JS_QUOTES_UNSPEC = 1; + JS_QUOTES_KEEP = 2; + JS_QUOTES_OMIT = 3; +} + +enum JsonTableColumnType +{ + JSON_TABLE_COLUMN_TYPE_UNDEFINED = 0; + JTC_FOR_ORDINALITY = 1; + JTC_REGULAR = 2; + JTC_EXISTS = 3; + JTC_FORMATTED = 4; + JTC_NESTED = 5; +} + +enum SetOperation +{ + SET_OPERATION_UNDEFINED = 0; + SETOP_NONE = 1; + SETOP_UNION = 2; + SETOP_INTERSECT = 3; + SETOP_EXCEPT = 4; +} + +enum ObjectType +{ + OBJECT_TYPE_UNDEFINED = 0; + OBJECT_ACCESS_METHOD = 1; + OBJECT_AGGREGATE = 2; + OBJECT_AMOP = 3; + OBJECT_AMPROC = 4; + OBJECT_ATTRIBUTE = 5; + OBJECT_CAST = 6; + OBJECT_COLUMN = 7; + OBJECT_COLLATION = 8; + OBJECT_CONVERSION = 9; + OBJECT_DATABASE = 10; + OBJECT_DEFAULT = 11; + OBJECT_DEFACL = 12; + OBJECT_DOMAIN = 13; + OBJECT_DOMCONSTRAINT = 14; + OBJECT_EVENT_TRIGGER = 15; + OBJECT_EXTENSION = 16; + OBJECT_FDW = 17; + OBJECT_FOREIGN_SERVER = 18; + OBJECT_FOREIGN_TABLE = 19; + OBJECT_FUNCTION = 20; + OBJECT_INDEX = 21; + OBJECT_LANGUAGE = 22; + OBJECT_LARGEOBJECT = 23; + OBJECT_MATVIEW = 24; + OBJECT_OPCLASS = 25; + OBJECT_OPERATOR = 26; + OBJECT_OPFAMILY = 27; + OBJECT_PARAMETER_ACL = 28; + OBJECT_POLICY = 29; + OBJECT_PROCEDURE = 30; + OBJECT_PUBLICATION = 31; + OBJECT_PUBLICATION_NAMESPACE = 32; + OBJECT_PUBLICATION_REL = 33; + OBJECT_ROLE = 34; + OBJECT_ROUTINE = 35; + OBJECT_RULE = 36; + OBJECT_SCHEMA = 37; + OBJECT_SEQUENCE = 38; + OBJECT_SUBSCRIPTION = 39; + OBJECT_STATISTIC_EXT = 40; + OBJECT_TABCONSTRAINT = 41; + OBJECT_TABLE = 42; + OBJECT_TABLESPACE = 43; + OBJECT_TRANSFORM = 44; + OBJECT_TRIGGER = 45; + OBJECT_TSCONFIGURATION = 46; + OBJECT_TSDICTIONARY = 47; + OBJECT_TSPARSER = 48; + OBJECT_TSTEMPLATE = 49; + OBJECT_TYPE = 50; + OBJECT_USER_MAPPING = 51; + OBJECT_VIEW = 52; +} + +enum DropBehavior +{ + DROP_BEHAVIOR_UNDEFINED = 0; + DROP_RESTRICT = 1; + DROP_CASCADE = 2; +} + +enum AlterTableType +{ + ALTER_TABLE_TYPE_UNDEFINED = 0; + AT_AddColumn = 1; + AT_AddColumnToView = 2; + AT_ColumnDefault = 3; + AT_CookedColumnDefault = 4; + AT_DropNotNull = 5; + AT_SetNotNull = 6; + AT_SetExpression = 7; + AT_DropExpression = 8; + AT_CheckNotNull = 9; + AT_SetStatistics = 10; + AT_SetOptions = 11; + AT_ResetOptions = 12; + AT_SetStorage = 13; + AT_SetCompression = 14; + AT_DropColumn = 15; + AT_AddIndex = 16; + AT_ReAddIndex = 17; + AT_AddConstraint = 18; + AT_ReAddConstraint = 19; + AT_ReAddDomainConstraint = 20; + AT_AlterConstraint = 21; + AT_ValidateConstraint = 22; + AT_AddIndexConstraint = 23; + AT_DropConstraint = 24; + AT_ReAddComment = 25; + AT_AlterColumnType = 26; + AT_AlterColumnGenericOptions = 27; + AT_ChangeOwner = 28; + AT_ClusterOn = 29; + AT_DropCluster = 30; + AT_SetLogged = 31; + AT_SetUnLogged = 32; + AT_DropOids = 33; + AT_SetAccessMethod = 34; + AT_SetTableSpace = 35; + AT_SetRelOptions = 36; + AT_ResetRelOptions = 37; + AT_ReplaceRelOptions = 38; + AT_EnableTrig = 39; + AT_EnableAlwaysTrig = 40; + AT_EnableReplicaTrig = 41; + AT_DisableTrig = 42; + AT_EnableTrigAll = 43; + AT_DisableTrigAll = 44; + AT_EnableTrigUser = 45; + AT_DisableTrigUser = 46; + AT_EnableRule = 47; + AT_EnableAlwaysRule = 48; + AT_EnableReplicaRule = 49; + AT_DisableRule = 50; + AT_AddInherit = 51; + AT_DropInherit = 52; + AT_AddOf = 53; + AT_DropOf = 54; + AT_ReplicaIdentity = 55; + AT_EnableRowSecurity = 56; + AT_DisableRowSecurity = 57; + AT_ForceRowSecurity = 58; + AT_NoForceRowSecurity = 59; + AT_GenericOptions = 60; + AT_AttachPartition = 61; + AT_DetachPartition = 62; + AT_DetachPartitionFinalize = 63; + AT_AddIdentity = 64; + AT_SetIdentity = 65; + AT_DropIdentity = 66; + AT_ReAddStatistics = 67; +} + +enum GrantTargetType +{ + GRANT_TARGET_TYPE_UNDEFINED = 0; + ACL_TARGET_OBJECT = 1; + ACL_TARGET_ALL_IN_SCHEMA = 2; + ACL_TARGET_DEFAULTS = 3; +} + +enum VariableSetKind +{ + VARIABLE_SET_KIND_UNDEFINED = 0; + VAR_SET_VALUE = 1; + VAR_SET_DEFAULT = 2; + VAR_SET_CURRENT = 3; + VAR_SET_MULTI = 4; + VAR_RESET = 5; + VAR_RESET_ALL = 6; +} + +enum ConstrType +{ + CONSTR_TYPE_UNDEFINED = 0; + CONSTR_NULL = 1; + CONSTR_NOTNULL = 2; + CONSTR_DEFAULT = 3; + CONSTR_IDENTITY = 4; + CONSTR_GENERATED = 5; + CONSTR_CHECK = 6; + CONSTR_PRIMARY = 7; + CONSTR_UNIQUE = 8; + CONSTR_EXCLUSION = 9; + CONSTR_FOREIGN = 10; + CONSTR_ATTR_DEFERRABLE = 11; + CONSTR_ATTR_NOT_DEFERRABLE = 12; + CONSTR_ATTR_DEFERRED = 13; + CONSTR_ATTR_IMMEDIATE = 14; +} + +enum ImportForeignSchemaType +{ + IMPORT_FOREIGN_SCHEMA_TYPE_UNDEFINED = 0; + FDW_IMPORT_SCHEMA_ALL = 1; + FDW_IMPORT_SCHEMA_LIMIT_TO = 2; + FDW_IMPORT_SCHEMA_EXCEPT = 3; +} + +enum RoleStmtType +{ + ROLE_STMT_TYPE_UNDEFINED = 0; + ROLESTMT_ROLE = 1; + ROLESTMT_USER = 2; + ROLESTMT_GROUP = 3; +} + +enum FetchDirection +{ + FETCH_DIRECTION_UNDEFINED = 0; + FETCH_FORWARD = 1; + FETCH_BACKWARD = 2; + FETCH_ABSOLUTE = 3; + FETCH_RELATIVE = 4; +} + +enum FunctionParameterMode +{ + FUNCTION_PARAMETER_MODE_UNDEFINED = 0; + FUNC_PARAM_IN = 1; + FUNC_PARAM_OUT = 2; + FUNC_PARAM_INOUT = 3; + FUNC_PARAM_VARIADIC = 4; + FUNC_PARAM_TABLE = 5; + FUNC_PARAM_DEFAULT = 6; +} + +enum TransactionStmtKind +{ + TRANSACTION_STMT_KIND_UNDEFINED = 0; + TRANS_STMT_BEGIN = 1; + TRANS_STMT_START = 2; + TRANS_STMT_COMMIT = 3; + TRANS_STMT_ROLLBACK = 4; + TRANS_STMT_SAVEPOINT = 5; + TRANS_STMT_RELEASE = 6; + TRANS_STMT_ROLLBACK_TO = 7; + TRANS_STMT_PREPARE = 8; + TRANS_STMT_COMMIT_PREPARED = 9; + TRANS_STMT_ROLLBACK_PREPARED = 10; +} + +enum ViewCheckOption +{ + VIEW_CHECK_OPTION_UNDEFINED = 0; + NO_CHECK_OPTION = 1; + LOCAL_CHECK_OPTION = 2; + CASCADED_CHECK_OPTION = 3; +} + +enum DiscardMode +{ + DISCARD_MODE_UNDEFINED = 0; + DISCARD_ALL = 1; + DISCARD_PLANS = 2; + DISCARD_SEQUENCES = 3; + DISCARD_TEMP = 4; +} + +enum ReindexObjectType +{ + REINDEX_OBJECT_TYPE_UNDEFINED = 0; + REINDEX_OBJECT_INDEX = 1; + REINDEX_OBJECT_TABLE = 2; + REINDEX_OBJECT_SCHEMA = 3; + REINDEX_OBJECT_SYSTEM = 4; + REINDEX_OBJECT_DATABASE = 5; +} + +enum AlterTSConfigType +{ + ALTER_TSCONFIG_TYPE_UNDEFINED = 0; + ALTER_TSCONFIG_ADD_MAPPING = 1; + ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN = 2; + ALTER_TSCONFIG_REPLACE_DICT = 3; + ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN = 4; + ALTER_TSCONFIG_DROP_MAPPING = 5; +} + +enum PublicationObjSpecType +{ + PUBLICATION_OBJ_SPEC_TYPE_UNDEFINED = 0; + PUBLICATIONOBJ_TABLE = 1; + PUBLICATIONOBJ_TABLES_IN_SCHEMA = 2; + PUBLICATIONOBJ_TABLES_IN_CUR_SCHEMA = 3; + PUBLICATIONOBJ_CONTINUATION = 4; +} + +enum AlterPublicationAction +{ + ALTER_PUBLICATION_ACTION_UNDEFINED = 0; + AP_AddObjects = 1; + AP_DropObjects = 2; + AP_SetObjects = 3; +} + +enum AlterSubscriptionType +{ + ALTER_SUBSCRIPTION_TYPE_UNDEFINED = 0; + ALTER_SUBSCRIPTION_OPTIONS = 1; + ALTER_SUBSCRIPTION_CONNECTION = 2; + ALTER_SUBSCRIPTION_SET_PUBLICATION = 3; + ALTER_SUBSCRIPTION_ADD_PUBLICATION = 4; + ALTER_SUBSCRIPTION_DROP_PUBLICATION = 5; + ALTER_SUBSCRIPTION_REFRESH = 6; + ALTER_SUBSCRIPTION_ENABLED = 7; + ALTER_SUBSCRIPTION_SKIP = 8; +} + +enum OverridingKind +{ + OVERRIDING_KIND_UNDEFINED = 0; + OVERRIDING_NOT_SET = 1; + OVERRIDING_USER_VALUE = 2; + OVERRIDING_SYSTEM_VALUE = 3; +} + +enum OnCommitAction +{ + ON_COMMIT_ACTION_UNDEFINED = 0; + ONCOMMIT_NOOP = 1; + ONCOMMIT_PRESERVE_ROWS = 2; + ONCOMMIT_DELETE_ROWS = 3; + ONCOMMIT_DROP = 4; +} + +enum TableFuncType +{ + TABLE_FUNC_TYPE_UNDEFINED = 0; + TFT_XMLTABLE = 1; + TFT_JSON_TABLE = 2; +} + +enum ParamKind +{ + PARAM_KIND_UNDEFINED = 0; + PARAM_EXTERN = 1; + PARAM_EXEC = 2; + PARAM_SUBLINK = 3; + PARAM_MULTIEXPR = 4; +} + +enum CoercionContext +{ + COERCION_CONTEXT_UNDEFINED = 0; + COERCION_IMPLICIT = 1; + COERCION_ASSIGNMENT = 2; + COERCION_PLPGSQL = 3; + COERCION_EXPLICIT = 4; +} + +enum CoercionForm +{ + COERCION_FORM_UNDEFINED = 0; + COERCE_EXPLICIT_CALL = 1; + COERCE_EXPLICIT_CAST = 2; + COERCE_IMPLICIT_CAST = 3; + COERCE_SQL_SYNTAX = 4; +} + +enum BoolExprType +{ + BOOL_EXPR_TYPE_UNDEFINED = 0; + AND_EXPR = 1; + OR_EXPR = 2; + NOT_EXPR = 3; +} + +enum SubLinkType +{ + SUB_LINK_TYPE_UNDEFINED = 0; + EXISTS_SUBLINK = 1; + ALL_SUBLINK = 2; + ANY_SUBLINK = 3; + ROWCOMPARE_SUBLINK = 4; + EXPR_SUBLINK = 5; + MULTIEXPR_SUBLINK = 6; + ARRAY_SUBLINK = 7; + CTE_SUBLINK = 8; +} + +enum RowCompareType +{ + ROW_COMPARE_TYPE_UNDEFINED = 0; + ROWCOMPARE_LT = 1; + ROWCOMPARE_LE = 2; + ROWCOMPARE_EQ = 3; + ROWCOMPARE_GE = 4; + ROWCOMPARE_GT = 5; + ROWCOMPARE_NE = 6; +} + +enum MinMaxOp +{ + MIN_MAX_OP_UNDEFINED = 0; + IS_GREATEST = 1; + IS_LEAST = 2; +} + +enum SQLValueFunctionOp +{ + SQLVALUE_FUNCTION_OP_UNDEFINED = 0; + SVFOP_CURRENT_DATE = 1; + SVFOP_CURRENT_TIME = 2; + SVFOP_CURRENT_TIME_N = 3; + SVFOP_CURRENT_TIMESTAMP = 4; + SVFOP_CURRENT_TIMESTAMP_N = 5; + SVFOP_LOCALTIME = 6; + SVFOP_LOCALTIME_N = 7; + SVFOP_LOCALTIMESTAMP = 8; + SVFOP_LOCALTIMESTAMP_N = 9; + SVFOP_CURRENT_ROLE = 10; + SVFOP_CURRENT_USER = 11; + SVFOP_USER = 12; + SVFOP_SESSION_USER = 13; + SVFOP_CURRENT_CATALOG = 14; + SVFOP_CURRENT_SCHEMA = 15; +} + +enum XmlExprOp +{ + XML_EXPR_OP_UNDEFINED = 0; + IS_XMLCONCAT = 1; + IS_XMLELEMENT = 2; + IS_XMLFOREST = 3; + IS_XMLPARSE = 4; + IS_XMLPI = 5; + IS_XMLROOT = 6; + IS_XMLSERIALIZE = 7; + IS_DOCUMENT = 8; +} + +enum XmlOptionType +{ + XML_OPTION_TYPE_UNDEFINED = 0; + XMLOPTION_DOCUMENT = 1; + XMLOPTION_CONTENT = 2; +} + +enum JsonEncoding +{ + JSON_ENCODING_UNDEFINED = 0; + JS_ENC_DEFAULT = 1; + JS_ENC_UTF8 = 2; + JS_ENC_UTF16 = 3; + JS_ENC_UTF32 = 4; +} + +enum JsonFormatType +{ + JSON_FORMAT_TYPE_UNDEFINED = 0; + JS_FORMAT_DEFAULT = 1; + JS_FORMAT_JSON = 2; + JS_FORMAT_JSONB = 3; +} + +enum JsonConstructorType +{ + JSON_CONSTRUCTOR_TYPE_UNDEFINED = 0; + JSCTOR_JSON_OBJECT = 1; + JSCTOR_JSON_ARRAY = 2; + JSCTOR_JSON_OBJECTAGG = 3; + JSCTOR_JSON_ARRAYAGG = 4; + JSCTOR_JSON_PARSE = 5; + JSCTOR_JSON_SCALAR = 6; + JSCTOR_JSON_SERIALIZE = 7; +} + +enum JsonValueType +{ + JSON_VALUE_TYPE_UNDEFINED = 0; + JS_TYPE_ANY = 1; + JS_TYPE_OBJECT = 2; + JS_TYPE_ARRAY = 3; + JS_TYPE_SCALAR = 4; +} + +enum JsonWrapper +{ + JSON_WRAPPER_UNDEFINED = 0; + JSW_UNSPEC = 1; + JSW_NONE = 2; + JSW_CONDITIONAL = 3; + JSW_UNCONDITIONAL = 4; +} + +enum JsonBehaviorType +{ + JSON_BEHAVIOR_TYPE_UNDEFINED = 0; + JSON_BEHAVIOR_NULL = 1; + JSON_BEHAVIOR_ERROR = 2; + JSON_BEHAVIOR_EMPTY = 3; + JSON_BEHAVIOR_TRUE = 4; + JSON_BEHAVIOR_FALSE = 5; + JSON_BEHAVIOR_UNKNOWN = 6; + JSON_BEHAVIOR_EMPTY_ARRAY = 7; + JSON_BEHAVIOR_EMPTY_OBJECT = 8; + JSON_BEHAVIOR_DEFAULT = 9; +} + +enum JsonExprOp +{ + JSON_EXPR_OP_UNDEFINED = 0; + JSON_EXISTS_OP = 1; + JSON_QUERY_OP = 2; + JSON_VALUE_OP = 3; + JSON_TABLE_OP = 4; +} + +enum NullTestType +{ + NULL_TEST_TYPE_UNDEFINED = 0; + IS_NULL = 1; + IS_NOT_NULL = 2; +} + +enum BoolTestType +{ + BOOL_TEST_TYPE_UNDEFINED = 0; + IS_TRUE = 1; + IS_NOT_TRUE = 2; + IS_FALSE = 3; + IS_NOT_FALSE = 4; + IS_UNKNOWN = 5; + IS_NOT_UNKNOWN = 6; +} + +enum MergeMatchKind +{ + MERGE_MATCH_KIND_UNDEFINED = 0; + MERGE_WHEN_MATCHED = 1; + MERGE_WHEN_NOT_MATCHED_BY_SOURCE = 2; + MERGE_WHEN_NOT_MATCHED_BY_TARGET = 3; +} + +enum CmdType +{ + CMD_TYPE_UNDEFINED = 0; + CMD_UNKNOWN = 1; + CMD_SELECT = 2; + CMD_UPDATE = 3; + CMD_INSERT = 4; + CMD_DELETE = 5; + CMD_MERGE = 6; + CMD_UTILITY = 7; + CMD_NOTHING = 8; +} + +enum JoinType +{ + JOIN_TYPE_UNDEFINED = 0; + JOIN_INNER = 1; + JOIN_LEFT = 2; + JOIN_FULL = 3; + JOIN_RIGHT = 4; + JOIN_SEMI = 5; + JOIN_ANTI = 6; + JOIN_RIGHT_ANTI = 7; + JOIN_UNIQUE_OUTER = 8; + JOIN_UNIQUE_INNER = 9; +} + +enum AggStrategy +{ + AGG_STRATEGY_UNDEFINED = 0; + AGG_PLAIN = 1; + AGG_SORTED = 2; + AGG_HASHED = 3; + AGG_MIXED = 4; +} + +enum AggSplit +{ + AGG_SPLIT_UNDEFINED = 0; + AGGSPLIT_SIMPLE = 1; + AGGSPLIT_INITIAL_SERIAL = 2; + AGGSPLIT_FINAL_DESERIAL = 3; +} + +enum SetOpCmd +{ + SET_OP_CMD_UNDEFINED = 0; + SETOPCMD_INTERSECT = 1; + SETOPCMD_INTERSECT_ALL = 2; + SETOPCMD_EXCEPT = 3; + SETOPCMD_EXCEPT_ALL = 4; +} + +enum SetOpStrategy +{ + SET_OP_STRATEGY_UNDEFINED = 0; + SETOP_SORTED = 1; + SETOP_HASHED = 2; +} + +enum OnConflictAction +{ + ON_CONFLICT_ACTION_UNDEFINED = 0; + ONCONFLICT_NONE = 1; + ONCONFLICT_NOTHING = 2; + ONCONFLICT_UPDATE = 3; +} + +enum LimitOption +{ + LIMIT_OPTION_UNDEFINED = 0; + LIMIT_OPTION_DEFAULT = 1; + LIMIT_OPTION_COUNT = 2; + LIMIT_OPTION_WITH_TIES = 3; +} + +enum LockClauseStrength +{ + LOCK_CLAUSE_STRENGTH_UNDEFINED = 0; + LCS_NONE = 1; + LCS_FORKEYSHARE = 2; + LCS_FORSHARE = 3; + LCS_FORNOKEYUPDATE = 4; + LCS_FORUPDATE = 5; +} + +enum LockWaitPolicy +{ + LOCK_WAIT_POLICY_UNDEFINED = 0; + LockWaitBlock = 1; + LockWaitSkip = 2; + LockWaitError = 3; +} + +enum LockTupleMode +{ + LOCK_TUPLE_MODE_UNDEFINED = 0; + LockTupleKeyShare = 1; + LockTupleShare = 2; + LockTupleNoKeyExclusive = 3; + LockTupleExclusive = 4; +} + +message ScanToken { + int32 start = 1; + int32 end = 2; + Token token = 4; + KeywordKind keyword_kind = 5; +} + +enum KeywordKind { + NO_KEYWORD = 0; + UNRESERVED_KEYWORD = 1; + COL_NAME_KEYWORD = 2; + TYPE_FUNC_NAME_KEYWORD = 3; + RESERVED_KEYWORD = 4; +} + +enum Token { + NUL = 0; + // Single-character tokens that are returned 1:1 (identical with "self" list in scan.l) + // Either supporting syntax, or single-character operators (some can be both) + // Also see https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-SPECIAL-CHARS + ASCII_36 = 36; // "$" + ASCII_37 = 37; // "%" + ASCII_40 = 40; // "(" + ASCII_41 = 41; // ")" + ASCII_42 = 42; // "*" + ASCII_43 = 43; // "+" + ASCII_44 = 44; // "," + ASCII_45 = 45; // "-" + ASCII_46 = 46; // "." + ASCII_47 = 47; // "/" + ASCII_58 = 58; // ":" + ASCII_59 = 59; // ";" + ASCII_60 = 60; // "<" + ASCII_61 = 61; // "=" + ASCII_62 = 62; // ">" + ASCII_63 = 63; // "?" + ASCII_91 = 91; // "[" + ASCII_92 = 92; // "\" + ASCII_93 = 93; // "]" + ASCII_94 = 94; // "^" + // Named tokens in scan.l + IDENT = 258; + UIDENT = 259; + FCONST = 260; + SCONST = 261; + USCONST = 262; + BCONST = 263; + XCONST = 264; + Op = 265; + ICONST = 266; + PARAM = 267; + TYPECAST = 268; + DOT_DOT = 269; + COLON_EQUALS = 270; + EQUALS_GREATER = 271; + LESS_EQUALS = 272; + GREATER_EQUALS = 273; + NOT_EQUALS = 274; + SQL_COMMENT = 275; + C_COMMENT = 276; + ABORT_P = 277; + ABSENT = 278; + ABSOLUTE_P = 279; + ACCESS = 280; + ACTION = 281; + ADD_P = 282; + ADMIN = 283; + AFTER = 284; + AGGREGATE = 285; + ALL = 286; + ALSO = 287; + ALTER = 288; + ALWAYS = 289; + ANALYSE = 290; + ANALYZE = 291; + AND = 292; + ANY = 293; + ARRAY = 294; + AS = 295; + ASC = 296; + ASENSITIVE = 297; + ASSERTION = 298; + ASSIGNMENT = 299; + ASYMMETRIC = 300; + ATOMIC = 301; + AT = 302; + ATTACH = 303; + ATTRIBUTE = 304; + AUTHORIZATION = 305; + BACKWARD = 306; + BEFORE = 307; + BEGIN_P = 308; + BETWEEN = 309; + BIGINT = 310; + BINARY = 311; + BIT = 312; + BOOLEAN_P = 313; + BOTH = 314; + BREADTH = 315; + BY = 316; + CACHE = 317; + CALL = 318; + CALLED = 319; + CASCADE = 320; + CASCADED = 321; + CASE = 322; + CAST = 323; + CATALOG_P = 324; + CHAIN = 325; + CHAR_P = 326; + CHARACTER = 327; + CHARACTERISTICS = 328; + CHECK = 329; + CHECKPOINT = 330; + CLASS = 331; + CLOSE = 332; + CLUSTER = 333; + COALESCE = 334; + COLLATE = 335; + COLLATION = 336; + COLUMN = 337; + COLUMNS = 338; + COMMENT = 339; + COMMENTS = 340; + COMMIT = 341; + COMMITTED = 342; + COMPRESSION = 343; + CONCURRENTLY = 344; + CONDITIONAL = 345; + CONFIGURATION = 346; + CONFLICT = 347; + CONNECTION = 348; + CONSTRAINT = 349; + CONSTRAINTS = 350; + CONTENT_P = 351; + CONTINUE_P = 352; + CONVERSION_P = 353; + COPY = 354; + COST = 355; + CREATE = 356; + CROSS = 357; + CSV = 358; + CUBE = 359; + CURRENT_P = 360; + CURRENT_CATALOG = 361; + CURRENT_DATE = 362; + CURRENT_ROLE = 363; + CURRENT_SCHEMA = 364; + CURRENT_TIME = 365; + CURRENT_TIMESTAMP = 366; + CURRENT_USER = 367; + CURSOR = 368; + CYCLE = 369; + DATA_P = 370; + DATABASE = 371; + DAY_P = 372; + DEALLOCATE = 373; + DEC = 374; + DECIMAL_P = 375; + DECLARE = 376; + DEFAULT = 377; + DEFAULTS = 378; + DEFERRABLE = 379; + DEFERRED = 380; + DEFINER = 381; + DELETE_P = 382; + DELIMITER = 383; + DELIMITERS = 384; + DEPENDS = 385; + DEPTH = 386; + DESC = 387; + DETACH = 388; + DICTIONARY = 389; + DISABLE_P = 390; + DISCARD = 391; + DISTINCT = 392; + DO = 393; + DOCUMENT_P = 394; + DOMAIN_P = 395; + DOUBLE_P = 396; + DROP = 397; + EACH = 398; + ELSE = 399; + EMPTY_P = 400; + ENABLE_P = 401; + ENCODING = 402; + ENCRYPTED = 403; + END_P = 404; + ENUM_P = 405; + ERROR_P = 406; + ESCAPE = 407; + EVENT = 408; + EXCEPT = 409; + EXCLUDE = 410; + EXCLUDING = 411; + EXCLUSIVE = 412; + EXECUTE = 413; + EXISTS = 414; + EXPLAIN = 415; + EXPRESSION = 416; + EXTENSION = 417; + EXTERNAL = 418; + EXTRACT = 419; + FALSE_P = 420; + FAMILY = 421; + FETCH = 422; + FILTER = 423; + FINALIZE = 424; + FIRST_P = 425; + FLOAT_P = 426; + FOLLOWING = 427; + FOR = 428; + FORCE = 429; + FOREIGN = 430; + FORMAT = 431; + FORWARD = 432; + FREEZE = 433; + FROM = 434; + FULL = 435; + FUNCTION = 436; + FUNCTIONS = 437; + GENERATED = 438; + GLOBAL = 439; + GRANT = 440; + GRANTED = 441; + GREATEST = 442; + GROUP_P = 443; + GROUPING = 444; + GROUPS = 445; + HANDLER = 446; + HAVING = 447; + HEADER_P = 448; + HOLD = 449; + HOUR_P = 450; + IDENTITY_P = 451; + IF_P = 452; + ILIKE = 453; + IMMEDIATE = 454; + IMMUTABLE = 455; + IMPLICIT_P = 456; + IMPORT_P = 457; + IN_P = 458; + INCLUDE = 459; + INCLUDING = 460; + INCREMENT = 461; + INDENT = 462; + INDEX = 463; + INDEXES = 464; + INHERIT = 465; + INHERITS = 466; + INITIALLY = 467; + INLINE_P = 468; + INNER_P = 469; + INOUT = 470; + INPUT_P = 471; + INSENSITIVE = 472; + INSERT = 473; + INSTEAD = 474; + INT_P = 475; + INTEGER = 476; + INTERSECT = 477; + INTERVAL = 478; + INTO = 479; + INVOKER = 480; + IS = 481; + ISNULL = 482; + ISOLATION = 483; + JOIN = 484; + JSON = 485; + JSON_ARRAY = 486; + JSON_ARRAYAGG = 487; + JSON_EXISTS = 488; + JSON_OBJECT = 489; + JSON_OBJECTAGG = 490; + JSON_QUERY = 491; + JSON_SCALAR = 492; + JSON_SERIALIZE = 493; + JSON_TABLE = 494; + JSON_VALUE = 495; + KEEP = 496; + KEY = 497; + KEYS = 498; + LABEL = 499; + LANGUAGE = 500; + LARGE_P = 501; + LAST_P = 502; + LATERAL_P = 503; + LEADING = 504; + LEAKPROOF = 505; + LEAST = 506; + LEFT = 507; + LEVEL = 508; + LIKE = 509; + LIMIT = 510; + LISTEN = 511; + LOAD = 512; + LOCAL = 513; + LOCALTIME = 514; + LOCALTIMESTAMP = 515; + LOCATION = 516; + LOCK_P = 517; + LOCKED = 518; + LOGGED = 519; + MAPPING = 520; + MATCH = 521; + MATCHED = 522; + MATERIALIZED = 523; + MAXVALUE = 524; + MERGE = 525; + MERGE_ACTION = 526; + METHOD = 527; + MINUTE_P = 528; + MINVALUE = 529; + MODE = 530; + MONTH_P = 531; + MOVE = 532; + NAME_P = 533; + NAMES = 534; + NATIONAL = 535; + NATURAL = 536; + NCHAR = 537; + NESTED = 538; + NEW = 539; + NEXT = 540; + NFC = 541; + NFD = 542; + NFKC = 543; + NFKD = 544; + NO = 545; + NONE = 546; + NORMALIZE = 547; + NORMALIZED = 548; + NOT = 549; + NOTHING = 550; + NOTIFY = 551; + NOTNULL = 552; + NOWAIT = 553; + NULL_P = 554; + NULLIF = 555; + NULLS_P = 556; + NUMERIC = 557; + OBJECT_P = 558; + OF = 559; + OFF = 560; + OFFSET = 561; + OIDS = 562; + OLD = 563; + OMIT = 564; + ON = 565; + ONLY = 566; + OPERATOR = 567; + OPTION = 568; + OPTIONS = 569; + OR = 570; + ORDER = 571; + ORDINALITY = 572; + OTHERS = 573; + OUT_P = 574; + OUTER_P = 575; + OVER = 576; + OVERLAPS = 577; + OVERLAY = 578; + OVERRIDING = 579; + OWNED = 580; + OWNER = 581; + PARALLEL = 582; + PARAMETER = 583; + PARSER = 584; + PARTIAL = 585; + PARTITION = 586; + PASSING = 587; + PASSWORD = 588; + PATH = 589; + PLACING = 590; + PLAN = 591; + PLANS = 592; + POLICY = 593; + POSITION = 594; + PRECEDING = 595; + PRECISION = 596; + PRESERVE = 597; + PREPARE = 598; + PREPARED = 599; + PRIMARY = 600; + PRIOR = 601; + PRIVILEGES = 602; + PROCEDURAL = 603; + PROCEDURE = 604; + PROCEDURES = 605; + PROGRAM = 606; + PUBLICATION = 607; + QUOTE = 608; + QUOTES = 609; + RANGE = 610; + READ = 611; + REAL = 612; + REASSIGN = 613; + RECHECK = 614; + RECURSIVE = 615; + REF_P = 616; + REFERENCES = 617; + REFERENCING = 618; + REFRESH = 619; + REINDEX = 620; + RELATIVE_P = 621; + RELEASE = 622; + RENAME = 623; + REPEATABLE = 624; + REPLACE = 625; + REPLICA = 626; + RESET = 627; + RESTART = 628; + RESTRICT = 629; + RETURN = 630; + RETURNING = 631; + RETURNS = 632; + REVOKE = 633; + RIGHT = 634; + ROLE = 635; + ROLLBACK = 636; + ROLLUP = 637; + ROUTINE = 638; + ROUTINES = 639; + ROW = 640; + ROWS = 641; + RULE = 642; + SAVEPOINT = 643; + SCALAR = 644; + SCHEMA = 645; + SCHEMAS = 646; + SCROLL = 647; + SEARCH = 648; + SECOND_P = 649; + SECURITY = 650; + SELECT = 651; + SEQUENCE = 652; + SEQUENCES = 653; + SERIALIZABLE = 654; + SERVER = 655; + SESSION = 656; + SESSION_USER = 657; + SET = 658; + SETS = 659; + SETOF = 660; + SHARE = 661; + SHOW = 662; + SIMILAR = 663; + SIMPLE = 664; + SKIP = 665; + SMALLINT = 666; + SNAPSHOT = 667; + SOME = 668; + SOURCE = 669; + SQL_P = 670; + STABLE = 671; + STANDALONE_P = 672; + START = 673; + STATEMENT = 674; + STATISTICS = 675; + STDIN = 676; + STDOUT = 677; + STORAGE = 678; + STORED = 679; + STRICT_P = 680; + STRING_P = 681; + STRIP_P = 682; + SUBSCRIPTION = 683; + SUBSTRING = 684; + SUPPORT = 685; + SYMMETRIC = 686; + SYSID = 687; + SYSTEM_P = 688; + SYSTEM_USER = 689; + TABLE = 690; + TABLES = 691; + TABLESAMPLE = 692; + TABLESPACE = 693; + TARGET = 694; + TEMP = 695; + TEMPLATE = 696; + TEMPORARY = 697; + TEXT_P = 698; + THEN = 699; + TIES = 700; + TIME = 701; + TIMESTAMP = 702; + TO = 703; + TRAILING = 704; + TRANSACTION = 705; + TRANSFORM = 706; + TREAT = 707; + TRIGGER = 708; + TRIM = 709; + TRUE_P = 710; + TRUNCATE = 711; + TRUSTED = 712; + TYPE_P = 713; + TYPES_P = 714; + UESCAPE = 715; + UNBOUNDED = 716; + UNCONDITIONAL = 717; + UNCOMMITTED = 718; + UNENCRYPTED = 719; + UNION = 720; + UNIQUE = 721; + UNKNOWN = 722; + UNLISTEN = 723; + UNLOGGED = 724; + UNTIL = 725; + UPDATE = 726; + USER = 727; + USING = 728; + VACUUM = 729; + VALID = 730; + VALIDATE = 731; + VALIDATOR = 732; + VALUE_P = 733; + VALUES = 734; + VARCHAR = 735; + VARIADIC = 736; + VARYING = 737; + VERBOSE = 738; + VERSION_P = 739; + VIEW = 740; + VIEWS = 741; + VOLATILE = 742; + WHEN = 743; + WHERE = 744; + WHITESPACE_P = 745; + WINDOW = 746; + WITH = 747; + WITHIN = 748; + WITHOUT = 749; + WORK = 750; + WRAPPER = 751; + WRITE = 752; + XML_P = 753; + XMLATTRIBUTES = 754; + XMLCONCAT = 755; + XMLELEMENT = 756; + XMLEXISTS = 757; + XMLFOREST = 758; + XMLNAMESPACES = 759; + XMLPARSE = 760; + XMLPI = 761; + XMLROOT = 762; + XMLSERIALIZE = 763; + XMLTABLE = 764; + YEAR_P = 765; + YES_P = 766; + ZONE = 767; + FORMAT_LA = 768; + NOT_LA = 769; + NULLS_LA = 770; + WITH_LA = 771; + WITHOUT_LA = 772; + MODE_TYPE_NAME = 773; + MODE_PLPGSQL_EXPR = 774; + MODE_PLPGSQL_ASSIGN1 = 775; + MODE_PLPGSQL_ASSIGN2 = 776; + MODE_PLPGSQL_ASSIGN3 = 777; + UMINUS = 778; +} diff --git a/crates/pgt_pretty_print_codegen/src/group_kind.rs b/crates/pgt_pretty_print_codegen/src/group_kind.rs new file mode 100644 index 00000000..52fe25d0 --- /dev/null +++ b/crates/pgt_pretty_print_codegen/src/group_kind.rs @@ -0,0 +1,24 @@ +use quote::{format_ident, quote}; + +use crate::proto_analyser::ProtoAnalyzer; + +pub fn group_kind_mod(analyser: ProtoAnalyzer) -> proc_macro2::TokenStream { + let node_variants = analyser.enum_variants(); + + let mut node_enum_variants = Vec::new(); + + for variant in &node_variants { + let variant_ident = format_ident!("{}", &variant.name); + + node_enum_variants.push(quote! { + #variant_ident + }); + } + + quote! { + #[derive(Clone, PartialEq, Debug)] + pub enum GroupKind { + #(#node_enum_variants),*, + } + } +} diff --git a/crates/pgt_pretty_print_codegen/src/lib.rs b/crates/pgt_pretty_print_codegen/src/lib.rs index b8f3594e..5df181b8 100644 --- a/crates/pgt_pretty_print_codegen/src/lib.rs +++ b/crates/pgt_pretty_print_codegen/src/lib.rs @@ -1,9 +1,24 @@ +mod group_kind; mod keywords; +mod proto_analyser; mod token_kind; +use std::path; + +use proto_analyser::ProtoAnalyzer; use token_kind::token_kind_mod; #[proc_macro] pub fn token_kind_codegen(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { token_kind_mod().into() } + +#[proc_macro] +pub fn group_kind_codegen(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let analyser = ProtoAnalyzer::from(&proto_file_path()).unwrap(); + group_kind::group_kind_mod(analyser).into() +} + +fn proto_file_path() -> path::PathBuf { + path::PathBuf::from(env!("PG_QUERY_PROTO_PATH")) +} diff --git a/crates/pgt_pretty_print_codegen/src/proto_analyser.rs b/crates/pgt_pretty_print_codegen/src/proto_analyser.rs new file mode 100644 index 00000000..28abdf3e --- /dev/null +++ b/crates/pgt_pretty_print_codegen/src/proto_analyser.rs @@ -0,0 +1,52 @@ +use std::path::Path; + +use convert_case::{Case, Casing}; +use prost_reflect::{DescriptorError, DescriptorPool}; + +pub(crate) struct ProtoAnalyzer { + pool: DescriptorPool, +} + +pub(crate) struct EnumVariant { + pub name: String, +} + +impl ProtoAnalyzer { + pub fn from(proto_file: &Path) -> Result { + let include_path = proto_file + .parent() + .expect("Proto file must have a parent directory"); + + // protox::compile expects the proto file to be relative to the include path + let file_name = proto_file + .file_name() + .expect("Proto file must have a file name"); + + let pool = DescriptorPool::from_file_descriptor_set( + protox::compile([file_name], [include_path]).expect("unable to parse"), + )?; + + let analyzer = ProtoAnalyzer { pool }; + + Ok(analyzer) + } + + pub fn enum_variants(&self) -> Vec { + let node = self + .pool + .get_message_by_name(".pg_query.Node") + .expect("Node message not found"); + + let mut variants = Vec::new(); + for field in node.fields() { + // The prost-generated variant name is derived from the field name using snake_case to PascalCase conversion + // For example: ctesearch_clause -> CtesearchClause + let field_name = field.name(); + let variant_name = field_name.to_case(Case::Pascal); + + variants.push(EnumVariant { name: variant_name }); + } + + variants + } +} diff --git a/crates/pgt_pretty_print_codegen/src/token_kind.rs b/crates/pgt_pretty_print_codegen/src/token_kind.rs index 85eab61a..a2773ce3 100644 --- a/crates/pgt_pretty_print_codegen/src/token_kind.rs +++ b/crates/pgt_pretty_print_codegen/src/token_kind.rs @@ -31,6 +31,7 @@ const LITERALS: &[&str] = &[ "NULL", "STRING", "IDENT", + "BOOLEAN", ]; const VARIANT_DATA: &[(&str, &str)] = &[ @@ -44,6 +45,7 @@ const VARIANT_DATA: &[(&str, &str)] = &[ ("IDENT", "String"), // user_id, table_name ("POSITIONAL_PARAM", "u32"), // $1, $2, $3 (the number matters!) ("COMMENT", "String"), // /* comment text */ + ("BOOLEAN", "bool"), // true, false ]; pub fn token_kind_mod() -> proc_macro2::TokenStream { @@ -141,6 +143,10 @@ pub fn token_kind_mod() -> proc_macro2::TokenStream { TokenKind::FLOAT_NUMBER(n) => n.to_string(), TokenKind::BIT_STRING(s) => s.clone(), TokenKind::BYTE_STRING(s) => s.clone(), + TokenKind::BOOLEAN(b) => match b { + true => "TRUE".to_string(), + false => "FALSE".to_string(), + }, TokenKind::NULL => "NULL".to_string(), #(#render_kw_match_arms),*, _ => format!("{:?}", self), // Fallback for other variants diff --git a/xtask/agentic/src/autonomous_pretty_print.rs b/xtask/agentic/src/autonomous_pretty_print.rs index 129bff42..9ef37cae 100644 --- a/xtask/agentic/src/autonomous_pretty_print.rs +++ b/xtask/agentic/src/autonomous_pretty_print.rs @@ -114,7 +114,6 @@ Your goal is to continuously implement ToTokens for all unimplemented nodes unti ## CRITICAL RULES: - **NEVER EVER add comments to Rust code** - ZERO // comments, ZERO /* */ comments in implementations -- **Only add this separator**: // Implementation for NodeName - **NEVER manually implement nested nodes** - Always call .to_tokens(e) on child nodes, never implement their logic - **Use existing ToTokens implementations** - If a node already has ToTokens, call it, don't reimplement - **Start with simplest possible implementations** - use todo!() for complex fields initially @@ -122,6 +121,14 @@ Your goal is to continuously implement ToTokens for all unimplemented nodes unti - **When tests fail**, analyze the AST diff and fix the missing ToTokens fields - **Prioritize Stmt nodes first** (SelectStmt, InsertStmt, etc.) +## GROUP AND CONTEXT SYSTEM: + +- **Complex nodes MUST create groups**: `e.group_start(GroupKind::NodeName, None, false);` and `e.group_end();` +- **Simple nodes NO groups**: String, AConst, and other leaf nodes emit tokens directly +- **Context-aware formatting**: Use `e.is_within_group(GroupKind::ParentType)` to adapt behavior +- **Semicolons for top-level statements**: Use `if e.is_top_level() { e.token(TokenKind::SEMICOLON); }` +- **Never maintain explicit context state** - always introspect the event stream + ## FORBIDDEN PATTERNS: - `// Handle XYZ formatting directly` - NEVER add explanatory comments - `if let Some(node::Node::SomeType(inner)) = &node.node {{{{ /* manual implementation */ }}}}` - NEVER manually implement child nodes @@ -170,14 +177,43 @@ After each cycle, respond with just: ## CORRECT IMPLEMENTATION PATTERN: ```rust -impl ToTokens for SomeNode {{ - fn to_tokens(&self, e: &mut Elements) {{ - // Implementation for SomeNode +// Complex node with group +impl ToTokens for SomeComplexNode {{ + fn to_tokens(&self, e: &mut EventEmitter) {{ + e.group_start(GroupKind::SomeComplexNode, None, false); + + e.token(TokenKind::KEYWORD("SELECT".to_string())); if let Some(ref child) = self.some_child {{ child.to_tokens(e); // ✅ CORRECT - delegate to existing ToTokens }} - e.token(TokenKind::KEYWORD("SELECT".to_string())); - // NO COMMENTS ANYWHERE ELSE + + if e.is_top_level() {{ + e.token(TokenKind::SEMICOLON); + }} + + e.group_end(); + }} +}} + +// Simple leaf node without group +impl ToTokens for SomeSimpleNode {{ + fn to_tokens(&self, e: &mut EventEmitter) {{ + e.token(TokenKind::IDENT(self.value.clone())); + }} +}} + +// Context-aware node +impl ToTokens for ContextSensitiveNode {{ + fn to_tokens(&self, e: &mut EventEmitter) {{ + e.group_start(GroupKind::ContextSensitiveNode, None, false); + + if e.is_within_group(GroupKind::UpdateStmt) {{ + // UPDATE SET formatting: column = value + }} else {{ + // SELECT formatting: value AS alias + }} + + e.group_end(); }} }} ``` @@ -185,8 +221,9 @@ impl ToTokens for SomeNode {{ ## WRONG IMPLEMENTATION PATTERN (FORBIDDEN): ```rust impl ToTokens for SomeNode {{ - fn to_tokens(&self, e: &mut Elements) {{ - // Implementation for SomeNode + fn to_tokens(&self, e: &mut EventEmitter) {{ + e.group_start(GroupKind::SomeNode, None, false); + if let Some(ref child) = self.some_child {{ // Handle child formatting directly ❌ FORBIDDEN COMMENT if let Some(node::Node::ChildType(inner)) = &child.node {{ @@ -194,6 +231,8 @@ impl ToTokens for SomeNode {{ e.token(TokenKind::IDENT(inner.name.clone())); }} }} + + e.group_end(); }} }} ``` @@ -201,7 +240,12 @@ impl ToTokens for SomeNode {{ ## ACTION PLAN: Start by reading nodes.rs to see current state, then begin the implementation loop. Work systematically through the nodes list, implementing ToTokens for each one with proper error handling and testing. -ALWAYS delegate to existing ToTokens implementations. NEVER add explanatory comments. +## KEY CONTEXT RULES: +- **Complex nodes** (statements, expressions, structured data) MUST use groups with GroupKind +- **Simple leaf nodes** (String, AConst) emit tokens directly without groups +- **Context-sensitive nodes** use `e.is_within_group(GroupKind::ParentType)` to adapt formatting +- **Top-level statements** use `e.is_top_level()` to add semicolons +- **Always delegate** to existing ToTokens implementations. NEVER add explanatory comments. Continue this loop indefinitely until all nodes are implemented and all tests pass. "#, From 2b935e91c89118602638b5feac75844dc0d7d8bc Mon Sep 17 00:00:00 2001 From: psteinroe Date: Fri, 29 Aug 2025 14:08:45 +0200 Subject: [PATCH 15/15] progress --- crates/pgt_pretty_print/INTEGRATION_PLAN.md | 165 ------------------ ...t_simple.sql => alter_table_stmt_0_60.sql} | 0 ...e_stmt_simple.sql => create_stmt_0_60.sql} | 0 ...e_stmt_simple.sql => delete_stmt_0_60.sql} | 0 ...rop_stmt_simple.sql => drop_stmt_0_60.sql} | 0 ...t_stmt_simple.sql => insert_stmt_0_60.sql} | 0 .../tests/data/long_columns_0_60.sql | 1 + .../tests/data/long_select_0_60.sql | 1 + ...stmt_simple.sql => truncate_stmt_0_60.sql} | 0 ...e_stmt_simple.sql => update_stmt_0_60.sql} | 0 ...iew_stmt_simple.sql => view_stmt_0_60.sql} | 0 .../tests__alter_table_stmt_0_60.snap | 6 + .../snapshots/tests__create_stmt_0_60.snap | 6 + .../snapshots/tests__delete_stmt_0_60.snap | 6 + .../snapshots/tests__drop_stmt_0_60.snap | 6 + .../snapshots/tests__insert_stmt_0_60.snap | 6 + .../snapshots/tests__long_columns_0_60.snap | 12 ++ .../snapshots/tests__truncate_stmt_0_60.snap | 6 + .../snapshots/tests__update_stmt_0_60.snap | 6 + .../snapshots/tests__view_stmt_0_60.snap | 6 + xtask/agentic/src/autonomous_pretty_print.rs | 13 +- 21 files changed, 69 insertions(+), 171 deletions(-) delete mode 100644 crates/pgt_pretty_print/INTEGRATION_PLAN.md rename crates/pgt_pretty_print/tests/data/{alter_table_stmt_simple.sql => alter_table_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{create_stmt_simple.sql => create_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{delete_stmt_simple.sql => delete_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{drop_stmt_simple.sql => drop_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{insert_stmt_simple.sql => insert_stmt_0_60.sql} (100%) create mode 100644 crates/pgt_pretty_print/tests/data/long_columns_0_60.sql create mode 100644 crates/pgt_pretty_print/tests/data/long_select_0_60.sql rename crates/pgt_pretty_print/tests/data/{truncate_stmt_simple.sql => truncate_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{update_stmt_simple.sql => update_stmt_0_60.sql} (100%) rename crates/pgt_pretty_print/tests/data/{view_stmt_simple.sql => view_stmt_0_60.sql} (100%) create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__long_columns_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_0_60.snap create mode 100644 crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_0_60.snap diff --git a/crates/pgt_pretty_print/INTEGRATION_PLAN.md b/crates/pgt_pretty_print/INTEGRATION_PLAN.md deleted file mode 100644 index 184603a7..00000000 --- a/crates/pgt_pretty_print/INTEGRATION_PLAN.md +++ /dev/null @@ -1,165 +0,0 @@ -# PostgreSQL Pretty Printer Integration Plan - -## Current Status - -The pretty printer foundation is **complete and working**! Basic SQL formatting is functional with: -- ✅ SELECT statements with aliases, schema qualification -- ✅ Line length-based breaking (configurable via filename suffix) -- ✅ Proper comma placement and indentation -- ✅ Comprehensive test suite with snapshot testing -- ✅ AST integrity verification (location-aware comparison) - -## Architecture Overview - -``` -SQL Input → pgt_query::parse() → AST → ToTokens → Layout Events → Renderer → Formatted SQL -``` - -**Key Components:** -- **ToTokens trait**: Converts AST nodes to layout events -- **Layout Events**: `Token`, `Space`, `Line(Hard/Soft/SoftOrSpace)`, `GroupStart/End`, `IndentStart/End` -- **Renderer**: Two-phase prettier-style algorithm (try single line, else break) - -## Renderer Implementation Status - -### ✅ Completed -- **Core rendering pipeline**: Event processing, text/space/line output -- **Basic grouping**: Single-line vs multi-line decisions -- **Indentation**: Configurable spaces/tabs with proper nesting -- **Line length enforcement**: Respects `max_line_length` config -- **Token rendering**: Keywords, identifiers, punctuation -- **Break propagation**: Child groups with `break_parent: true` force parent groups to break -- **Nested group independence**: Inner groups make independent fit decisions when outer groups break -- **Stack overflow elimination**: Fixed infinite recursion in renderer - -### ❌ Missing Features (Priority Order) - -#### 1. **Group ID References** (Medium Priority) -**Issue**: Groups can't reference each other's break decisions. - -```rust -// Missing: Conditional formatting based on other groups -GroupStart { id: Some("params") } -// ... later reference "params" group's break decision -``` - -**Implementation**: -- Track group break decisions by ID -- Add conditional breaking logic - -#### 2. **Advanced Line Types** (Medium Priority) -**Issue**: `LineType::Soft` vs `LineType::SoftOrSpace` handling could be more sophisticated. - -**Current behavior**: -- `Hard`: Always breaks -- `Soft`: Breaks if group breaks, disappears if inline -- `SoftOrSpace`: Breaks if group breaks, becomes space if inline - -**Enhancement**: Better handling of soft line semantics in complex nesting. - -#### 3. **Performance Optimizations** (Low Priority) -- **Early bailout**: Stop single-line calculation when length exceeds limit -- **Caching**: Memoize group fit calculations for repeated structures -- **String building**: More efficient string concatenation - -## AST Node Coverage Status - -### ✅ Implemented ToTokens -- `SelectStmt`: Basic SELECT with FROM clause -- `ResTarget`: Column targets with aliases -- `ColumnRef`: Column references (schema.table.column) -- `String`: String literals in column references -- `RangeVar`: Table references with schema -- `FuncCall`: Function calls with break propagation support - -### ❌ Missing ToTokens (Add as needed) -- `InsertStmt`, `UpdateStmt`, `DeleteStmt`: DML statements -- `WhereClause`, `JoinExpr`: WHERE conditions and JOINs -- `AExpr`: Binary/unary expressions (`a = b`, `a + b`) -- `AConst`: Literals (numbers, strings, booleans) -- `SubLink`: Subqueries -- `CaseExpr`: CASE expressions -- `WindowFunc`: Window functions -- `AggRef`: Aggregate functions -- `TypeCast`: Type casting (`::int`) - -## Testing Infrastructure - -### ✅ Current -- **dir-test integration**: Drop SQL files → automatic snapshot testing -- **Line length extraction**: `filename_80.sql` → `max_line_length: 80` -- **AST integrity verification**: Ensures no data loss during formatting -- **Location field handling**: Clears location differences for comparison - -### 🔄 Enhancements Needed -- **Add more test cases**: Complex queries, edge cases -- **Performance benchmarks**: Large SQL file formatting speed -- **Configuration testing**: Different indent styles, line lengths -- **Break propagation testing**: Verified with `FuncCall` implementation - -## Integration Steps - -### ✅ Phase 1: Core Renderer Fixes (COMPLETED) -1. ✅ **Fix break propagation**: Implemented proper `break_parent` handling -2. ✅ **Fix nested groups**: Allow independent fit decisions -3. ✅ **Fix stack overflow**: Eliminated infinite recursion in renderer -4. ✅ **Test with complex cases**: Added `FuncCall` with break propagation test - -### Phase 2: AST Coverage Expansion (2-4 days) -1. **Add WHERE clause support**: `WhereClause`, `AExpr` ToTokens -2. **Add basic expressions**: `AConst`, binary operators -3. **Add INSERT/UPDATE/DELETE**: Basic DML statements - -### Phase 3: Advanced Features (1-2 days) -1. **Implement group ID system**: Cross-group references -2. **Add performance optimizations**: Early bailout, caching -3. **Enhanced line breaking**: Better soft line semantics - -### Phase 4: Production Ready (1-2 days) -1. **Comprehensive testing**: Large SQL files, edge cases -2. **Performance validation**: Benchmark against alternatives -3. **Documentation**: API docs, integration examples - -## API Integration Points - -```rust -// Main formatting function -pub fn format_sql(sql: &str, config: RenderConfig) -> Result { - let parsed = pgt_query::parse(sql)?; - let ast = parsed.root()?; - - let mut emitter = EventEmitter::new(); - ast.to_tokens(&mut emitter); - - let mut output = String::new(); - let mut renderer = Renderer::new(&mut output, config); - renderer.render(emitter.events)?; - - Ok(output) -} - -// Configuration -pub struct RenderConfig { - pub max_line_length: usize, // 80, 100, 120, etc. - pub indent_size: usize, // 2, 4, etc. - pub indent_style: IndentStyle, // Spaces, Tabs -} -``` - -## Estimated Completion Timeline - -- ✅ **Phase 1** (Core fixes): COMPLETED → **Fully functional renderer** -- **Phase 2** (AST coverage): 4 days → **Supports most common SQL** -- **Phase 3** (Advanced): 2 days → **Production-grade formatting** -- **Phase 4** (Polish): 2 days → **Integration ready** - -**Total: ~1 week remaining** for complete production-ready PostgreSQL pretty printer. - -## Current Limitations - -1. **Limited SQL coverage**: Only basic SELECT statements and function calls -2. **No error recovery**: Unimplemented AST nodes cause panics -3. **No configuration validation**: Invalid configs not checked -4. **Missing group ID system**: Cross-group conditional formatting not yet implemented - -The core renderer foundation is now solid with proper break propagation and nested group handling - the remaining work is primarily expanding AST node coverage. \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/alter_table_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/alter_table_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/alter_table_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/create_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/create_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/create_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/create_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/delete_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/delete_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/delete_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/drop_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/drop_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/drop_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/insert_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/insert_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/insert_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/long_columns_0_60.sql b/crates/pgt_pretty_print/tests/data/long_columns_0_60.sql new file mode 100644 index 00000000..56b6b2d7 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/long_columns_0_60.sql @@ -0,0 +1 @@ +SELECT customer_id, customer_name, customer_email, customer_phone FROM customer_table; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/long_select_0_60.sql b/crates/pgt_pretty_print/tests/data/long_select_0_60.sql new file mode 100644 index 00000000..7d5ce765 --- /dev/null +++ b/crates/pgt_pretty_print/tests/data/long_select_0_60.sql @@ -0,0 +1 @@ +SELECT first_name, last_name, email, phone_number, address FROM customers WHERE city = 'New York'; \ No newline at end of file diff --git a/crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/truncate_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/truncate_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/truncate_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/update_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/update_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/update_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/update_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/data/view_stmt_simple.sql b/crates/pgt_pretty_print/tests/data/view_stmt_0_60.sql similarity index 100% rename from crates/pgt_pretty_print/tests/data/view_stmt_simple.sql rename to crates/pgt_pretty_print/tests/data/view_stmt_0_60.sql diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_0_60.snap new file mode 100644 index 00000000..746dab7f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__alter_table_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/alter_table_stmt_0_60.sql +snapshot_kind: text +--- +ALTER TABLE users ADD COLUMN email text; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_0_60.snap new file mode 100644 index 00000000..d8310e4f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__create_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/create_stmt_0_60.sql +snapshot_kind: text +--- +CREATE TABLE users (id text, name text); diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_0_60.snap new file mode 100644 index 00000000..f5fa1d96 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__delete_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/delete_stmt_0_60.sql +snapshot_kind: text +--- +DELETE FROM users WHERE id = 1; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_0_60.snap new file mode 100644 index 00000000..e5ecf28b --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__drop_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/drop_stmt_0_60.sql +snapshot_kind: text +--- +DROP TABLE users; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_60.snap new file mode 100644 index 00000000..300f347f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__insert_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/insert_stmt_0_60.sql +snapshot_kind: text +--- +INSERT INTO users VALUES (1, 'John'); diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__long_columns_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__long_columns_0_60.snap new file mode 100644 index 00000000..01bb1a7f --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__long_columns_0_60.snap @@ -0,0 +1,12 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/long_columns_0_60.sql +snapshot_kind: text +--- +SELECT + customer_id, + customer_name, + customer_email, + customer_phone +FROM + customer_table; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_0_60.snap new file mode 100644 index 00000000..14e45c9a --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__truncate_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/truncate_stmt_0_60.sql +snapshot_kind: text +--- +TRUNCATE TABLE users; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_0_60.snap new file mode 100644 index 00000000..fb93863e --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__update_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/update_stmt_0_60.sql +snapshot_kind: text +--- +UPDATE users SET name = 'Jane Doe' WHERE id = 1; diff --git a/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_0_60.snap b/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_0_60.snap new file mode 100644 index 00000000..d5ed1f46 --- /dev/null +++ b/crates/pgt_pretty_print/tests/snapshots/tests__view_stmt_0_60.snap @@ -0,0 +1,6 @@ +--- +source: crates/pgt_pretty_print/tests/tests.rs +input_file: crates/pgt_pretty_print/tests/data/view_stmt_0_60.sql +snapshot_kind: text +--- +CREATE VIEW user_view AS SELECT id, name FROM users; diff --git a/xtask/agentic/src/autonomous_pretty_print.rs b/xtask/agentic/src/autonomous_pretty_print.rs index 9ef37cae..29ade6cc 100644 --- a/xtask/agentic/src/autonomous_pretty_print.rs +++ b/xtask/agentic/src/autonomous_pretty_print.rs @@ -100,8 +100,8 @@ Your goal is to continuously implement ToTokens for all unimplemented nodes unti 1. **Read the current nodes.rs file** to see what's already implemented 2. **Pick the next unimplemented node** from the AST nodes list 3. **Analyze the node structure** in crates/pgt_query/src/protobuf.rs -4. **Generate a minimal SQL test case** and write it to tests/data/ -5. **Validate the SQL example** by running: cargo test -p pgt_pretty_print --test tests validate_test_data__[filename] +4. **Generate a minimal SQL test case** and write it to tests/data/ with filename format: `[node_name]_[index]_60.sql` (60 is the line width for proper formatting) +5. **Validate the SQL example** by running: cargo test -p pgt_pretty_print --test tests validate_test_data__[filename_without_sql] 6. **Implement the ToTokens trait** for the node 7. **Add the node to the match statement** if not already there 8. **Run compilation checks** with cargo check -p pgt_pretty_print @@ -113,6 +113,7 @@ Your goal is to continuously implement ToTokens for all unimplemented nodes unti ## CRITICAL RULES: +- **ALWAYS use line width 60** in test filenames (e.g., `select_stmt_0_60.sql`) to ensure proper line breaking - **NEVER EVER add comments to Rust code** - ZERO // comments, ZERO /* */ comments in implementations - **NEVER manually implement nested nodes** - Always call .to_tokens(e) on child nodes, never implement their logic - **Use existing ToTokens implementations** - If a node already has ToTokens, call it, don't reimplement @@ -154,9 +155,9 @@ After creating a SQL file, validate it with: ```bash cargo test -p pgt_pretty_print --test tests validate_test_data__[filename_without_sql] ``` -Example: For insert_stmt_0_80.sql, run: +Example: For insert_stmt_0_60.sql, run: ```bash -cargo test -p pgt_pretty_print --test tests validate_test_data__insert_stmt_0_80 +cargo test -p pgt_pretty_print --test tests validate_test_data__insert_stmt_0_60 ``` This ensures the SQL parses correctly and produces the expected AST node type. @@ -167,8 +168,8 @@ After running formatter tests, check the quality by: 3. **Validate round-trip**: Parse the formatted SQL and compare AST structure with original 4. **Look for common issues**: Missing whitespace, incorrect operator precedence, malformed syntax -Example: For insert_stmt_0_80.sql, check: -`crates/pgt_pretty_print/tests/snapshots/tests__test_formatter__insert_stmt_0_80.snap` +Example: For insert_stmt_0_60.sql, check: +`crates/pgt_pretty_print/tests/snapshots/tests__test_formatter__insert_stmt_0_60.snap` ## RESPONSE FORMAT: After each cycle, respond with just: