|
| 1 | +# pgsql-parse |
| 2 | + |
| 3 | +<p align="center" width="100%"> |
| 4 | + <img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" /> |
| 5 | +</p> |
| 6 | + |
| 7 | +Comment and whitespace preserving PostgreSQL parser. A drop-in enhancement for `pgsql-parser` that preserves SQL comments (`--` line and `/* */` block) and vertical whitespace (blank lines) through parse-deparse round trips. |
| 8 | + |
| 9 | +## Installation |
| 10 | + |
| 11 | +```sh |
| 12 | +npm install pgsql-parse |
| 13 | +``` |
| 14 | + |
| 15 | +## Features |
| 16 | + |
| 17 | +* **Comment Preservation** -- Retains `--` line comments and `/* */` block comments through parse-deparse cycles |
| 18 | +* **Vertical Whitespace** -- Preserves blank lines between statements for readable output |
| 19 | +* **Idempotent Round-Trips** -- `parse -> deparse -> parse -> deparse` produces identical output |
| 20 | +* **Drop-in API** -- Re-exports `parse`, `parseSync`, `deparse`, `deparseSync`, `loadModule` from `pgsql-parser` |
| 21 | +* **Synthetic AST Nodes** -- `RawComment` and `RawWhitespace` nodes interleaved into the `stmts` array by byte position |
| 22 | + |
| 23 | +## How It Works |
| 24 | + |
| 25 | +1. A pure TypeScript scanner extracts comment and whitespace tokens with byte positions from the raw SQL text |
| 26 | +2. Enhanced `parse`/`parseSync` call the standard `libpg-query` parser, then interleave synthetic `RawComment` and `RawWhitespace` nodes into the `stmts` array based on byte position |
| 27 | +3. `deparseEnhanced()` dispatches on node type -- real `RawStmt` entries go through the standard deparser, while synthetic nodes emit their comment text or blank lines directly |
| 28 | + |
| 29 | +## API |
| 30 | + |
| 31 | +### Enhanced Parse |
| 32 | + |
| 33 | +```typescript |
| 34 | +import { parse, parseSync, deparseEnhanced, loadModule } from 'pgsql-parse'; |
| 35 | + |
| 36 | +// Async (handles initialization automatically) |
| 37 | +const result = await parse(` |
| 38 | +-- Create users table |
| 39 | +CREATE TABLE users (id serial PRIMARY KEY); |
| 40 | +
|
| 41 | +-- Create posts table |
| 42 | +CREATE TABLE posts (id serial PRIMARY KEY); |
| 43 | +`); |
| 44 | + |
| 45 | +// result.stmts contains RawComment, RawWhitespace, and RawStmt nodes |
| 46 | +const sql = deparseEnhanced(result); |
| 47 | +// Output preserves comments and blank lines |
| 48 | +``` |
| 49 | + |
| 50 | +### Sync Methods |
| 51 | + |
| 52 | +```typescript |
| 53 | +import { parseSync, deparseEnhanced, loadModule } from 'pgsql-parse'; |
| 54 | + |
| 55 | +await loadModule(); |
| 56 | + |
| 57 | +const result = parseSync('-- comment\nSELECT 1;'); |
| 58 | +const sql = deparseEnhanced(result); |
| 59 | +``` |
| 60 | + |
| 61 | +### Type Guards |
| 62 | + |
| 63 | +```typescript |
| 64 | +import { isRawComment, isRawWhitespace, isRawStmt } from 'pgsql-parse'; |
| 65 | + |
| 66 | +for (const stmt of result.stmts) { |
| 67 | + if (isRawComment(stmt)) { |
| 68 | + console.log('Comment:', stmt.RawComment.text); |
| 69 | + } else if (isRawWhitespace(stmt)) { |
| 70 | + console.log('Blank lines:', stmt.RawWhitespace.lines); |
| 71 | + } else if (isRawStmt(stmt)) { |
| 72 | + console.log('Statement:', stmt); |
| 73 | + } |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +## Credits |
| 78 | + |
| 79 | +Built on the excellent work of several contributors: |
| 80 | + |
| 81 | +* **[Dan Lynch](https://github.com/pyramation)** -- official maintainer since 2018 and architect of the current implementation |
| 82 | +* **[Lukas Fittl](https://github.com/lfittl)** for [libpg_query](https://github.com/pganalyze/libpg_query) -- the core PostgreSQL parser that powers this project |
0 commit comments