Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion DEPARSER_USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,75 @@ const options = {
const sql = deparse(parseResult, options);
```

### Pretty Formatting Options

The deparser supports pretty formatting to make SQL output more readable with proper indentation and line breaks:

```typescript
const options = {
pretty: true, // Enable pretty formatting (default: false)
newline: '\n', // Newline character (default: '\n')
tab: ' ', // Tab/indentation character (default: ' ')
functionDelimiter: '$$', // Function body delimiter (default: '$$')
functionDelimiterFallback: '$EOFCODE$' // Fallback delimiter (default: '$EOFCODE$')
};

const sql = deparse(parseResult, options);
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `pretty` | `boolean` | `false` | Enable pretty formatting with indentation and line breaks |
| `newline` | `string` | `'\n'` | Character(s) used for line breaks |
| `tab` | `string` | `' '` | Character(s) used for indentation (2 spaces by default) |
| `functionDelimiter` | `string` | `'$$'` | Delimiter used for function bodies |
| `functionDelimiterFallback` | `string` | `'$EOFCODE$'` | Alternative delimiter when default is found in function body |

#### Pretty Formatting Examples

**Basic SELECT with pretty formatting:**
```typescript
// Without pretty formatting
const sql1 = deparse(selectAst, { pretty: false });
// Output: "SELECT id, name, email FROM users WHERE active = true;"

// With pretty formatting
const sql2 = deparse(selectAst, { pretty: true });
// Output:
// SELECT
// id,
// name,
// email
// FROM users
// WHERE
// active = true;
```

**Custom formatting characters:**
```typescript
const options = {
pretty: true,
newline: '\r\n', // Windows line endings
tab: ' ' // 4-space indentation
};

const sql = deparse(parseResult, options);
```

**Supported Statements:**
Pretty formatting is supported for:
- `SELECT` statements with proper clause alignment
- `CREATE TABLE` statements with column definitions
- `CREATE POLICY` statements with clause formatting
- Common Table Expressions (CTEs)
- Constraint definitions
- JOIN operations with proper alignment

**Important Notes:**
- Pretty formatting preserves SQL semantics - the formatted SQL parses to the same AST
- Multi-line string literals are preserved without indentation to maintain their content
- Complex expressions maintain proper parentheses and operator precedence

## Instance Usage

You can also create a deparser instance:
Expand Down Expand Up @@ -185,4 +254,4 @@ const customSelect = {

const sql = deparse(customSelect);
// Output: "SELECT * FROM users"
```
```
21 changes: 21 additions & 0 deletions __fixtures__/pretty/constraints.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
total DECIMAL(10,2) CHECK (total > 0),
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT now(),
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT unique_user_date UNIQUE (user_id, created_at),
CONSTRAINT check_status CHECK (status IN ('pending', 'completed', 'cancelled'))
);

ALTER TABLE products ADD CONSTRAINT fk_category
FOREIGN KEY (category_id)
REFERENCES categories(id)
ON UPDATE CASCADE
ON DELETE SET NULL
DEFERRABLE INITIALLY DEFERRED;

ALTER TABLE products ADD CONSTRAINT check_price CHECK (price > 0);

ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email);
22 changes: 22 additions & 0 deletions __fixtures__/pretty/create_policy.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CREATE POLICY user_policy ON users FOR ALL TO authenticated_users USING (user_id = current_user_id());

CREATE POLICY admin_policy ON sensitive_data
AS RESTRICTIVE
FOR SELECT
TO admin_role
USING (department = current_user_department())
WITH CHECK (approved = true);

CREATE POLICY complex_policy ON documents
FOR UPDATE
TO document_editors
USING (
owner_id = current_user_id() OR
(shared = true AND permissions @> '{"edit": true}')
)
WITH CHECK (
status != 'archived' AND
last_modified > now() - interval '1 day'
);

CREATE POLICY simple_policy ON posts FOR SELECT TO public USING (published = true);
39 changes: 39 additions & 0 deletions __fixtures__/pretty/create_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
);

CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) CHECK (price > 0),
category_id INTEGER,
description TEXT,
created_at TIMESTAMP DEFAULT now(),
updated_at TIMESTAMP,
UNIQUE (name, category_id),
FOREIGN KEY (category_id) REFERENCES categories(id)
);

CREATE TABLE orders (
id SERIAL PRIMARY KEY,
subtotal DECIMAL(10,2) NOT NULL,
tax_rate DECIMAL(5,4) DEFAULT 0.0825,
tax_amount DECIMAL(10,2) GENERATED ALWAYS AS (subtotal * tax_rate) STORED,
total DECIMAL(10,2) GENERATED ALWAYS AS (subtotal + tax_amount) STORED
);

CREATE TABLE sales (
id SERIAL,
sale_date DATE NOT NULL,
amount DECIMAL(10,2),
region VARCHAR(50)
) PARTITION BY RANGE (sale_date);

CREATE TEMPORARY TABLE temp_calculations (
id INTEGER,
value DECIMAL(15,5),
result TEXT
);
29 changes: 29 additions & 0 deletions __fixtures__/pretty/select_statements.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
SELECT id, name, email FROM users WHERE active = true;

SELECT
u.id,
u.name,
u.email,
p.title as profile_title
FROM users u
JOIN profiles p ON u.id = p.user_id
WHERE u.active = true
AND u.created_at > '2023-01-01'
GROUP BY u.id, u.name, u.email, p.title
HAVING COUNT(*) > 1
ORDER BY u.created_at DESC, u.name ASC
LIMIT 10
OFFSET 5;

SELECT id, name FROM users WHERE id IN (
SELECT user_id FROM orders WHERE total > 100
);

SELECT name FROM customers
UNION ALL
SELECT name FROM suppliers
ORDER BY name;

SELECT name, email FROM users WHERE status = 'active';

SELECT u.name, o.total FROM users u, orders o WHERE u.id = o.user_id;
44 changes: 43 additions & 1 deletion packages/deparser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@ console.log(deparse(stmt));
// Output: SELECT * FROM another_table
```

## Options

The deparser accepts optional configuration for formatting and output control:

```ts
import { deparseSync as deparse } from 'pgsql-deparser';

const options = {
pretty: true, // Enable pretty formatting (default: false)
newline: '\n', // Newline character (default: '\n')
tab: ' ', // Tab/indentation character (default: ' ')
semicolons: true // Add semicolons to statements (default: true)
};

const sql = deparse(ast, options);
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `pretty` | `boolean` | `false` | Enable pretty formatting with indentation and line breaks |
| `newline` | `string` | `'\n'` | Character(s) used for line breaks |
| `tab` | `string` | `' '` | Character(s) used for indentation |
| `semicolons` | `boolean` | `true` | Add semicolons to SQL statements |

**Pretty formatting example:**
```ts
// Without pretty formatting
const sql1 = deparse(selectAst, { pretty: false });
// "SELECT id, name FROM users WHERE active = true;"

// With pretty formatting
const sql2 = deparse(selectAst, { pretty: true });
// SELECT
// id,
// name
// FROM users
// WHERE
// active = true;
```

For complete documentation and advanced options, see [DEPARSER_USAGE.md](../../DEPARSER_USAGE.md).

## Why Use `pgsql-deparser`?

`pgsql-deparser` is particularly useful in development environments where native dependencies are problematic or in applications where only the deparser functionality is required. Its independence from the full `pgsql-parser` package allows for more focused and lightweight SQL generation tasks.
Expand Down Expand Up @@ -98,4 +140,4 @@ Built on the excellent work of several contributors:

AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.

No developer or entity involved in creating Software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Software code or Software CLI, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
No developer or entity involved in creating Software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the Software code or Software CLI, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Pretty constraint formatting should format check constraint with pretty option enabled 1`] = `"ALTER TABLE products ADD CONSTRAINT check_price CHECK (price > 0);"`;

exports[`Pretty constraint formatting should format complex table with constraints with pretty option enabled 1`] = `
"CREATE TABLE orders (
id serial PRIMARY KEY,
user_id int NOT NULL,
total numeric(10, 2) CHECK (total > 0),
status varchar(20) DEFAULT 'pending',
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id)
ON DELETE CASCADE
);"
`;

exports[`Pretty constraint formatting should format foreign key constraint with pretty option enabled 1`] = `
"ALTER TABLE products ADD CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES categories (id)
ON UPDATE CASCADE
ON DELETE SET NULL
DEFERRABLE
INITIALLY DEFERRED;"
`;

exports[`Pretty constraint formatting should maintain single-line format for complex table when pretty disabled 1`] = `"CREATE TABLE orders (id serial PRIMARY KEY, user_id int NOT NULL, total numeric(10, 2) CHECK (total > 0), status varchar(20) DEFAULT 'pending', CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE);"`;

exports[`Pretty constraint formatting should maintain single-line format when pretty option disabled 1`] = `"ALTER TABLE products ADD CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES categories (id) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;"`;

exports[`Pretty constraint formatting should use custom newline and tab characters in pretty mode 1`] = `
"ALTER TABLE products ADD CONSTRAINT fk_category FOREIGN KEY (category_id) REFERENCES categories (id)
ON UPDATE CASCADE
ON DELETE SET NULL
DEFERRABLE
INITIALLY DEFERRED;"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Pretty CREATE POLICY formatting should format basic CREATE POLICY with pretty option enabled 1`] = `
"CREATE POLICY "user_policy"
ON users
AS PERMISSIVE
FOR ALL
TO authenticated_users
USING (
user_id = current_user_id()
);"
`;

exports[`Pretty CREATE POLICY formatting should format complex CREATE POLICY with pretty option enabled 1`] = `
"CREATE POLICY "admin_policy"
ON sensitive_data
AS RESTRICTIVE
FOR SELECT
TO admin_role
USING (
department = current_user_department()
)
WITH CHECK (
approved = true
);"
`;

exports[`Pretty CREATE POLICY formatting should format simple CREATE POLICY with pretty option enabled 1`] = `
"CREATE POLICY "simple_policy"
ON posts
AS PERMISSIVE
FOR SELECT
TO public
USING (
published = true
);"
`;

exports[`Pretty CREATE POLICY formatting should format very complex CREATE POLICY with pretty option enabled 1`] = `
"CREATE POLICY "complex_policy"
ON sensitive_data
AS RESTRICTIVE
FOR SELECT
TO admin_role
USING (
department = current_user_department()
AND EXISTS (SELECT
1
FROM user_permissions
WHERE
(user_id = current_user_id()
AND permission = 'read_sensitive'))
)
WITH CHECK (
approved = true
AND created_by = current_user_id()
);"
`;

exports[`Pretty CREATE POLICY formatting should maintain single-line format for complex policy when pretty disabled 1`] = `"CREATE POLICY "admin_policy" ON sensitive_data AS RESTRICTIVE FOR SELECT TO admin_role USING (department = current_user_department()) WITH CHECK (approved = true);"`;

exports[`Pretty CREATE POLICY formatting should maintain single-line format when pretty option disabled 1`] = `"CREATE POLICY "user_policy" ON users AS PERMISSIVE FOR ALL TO authenticated_users USING (user_id = current_user_id());"`;

exports[`Pretty CREATE POLICY formatting should use custom newline and tab characters in pretty mode 1`] = `
"CREATE POLICY "user_policy"
ON users
AS PERMISSIVE
FOR ALL
TO authenticated_users
USING (
user_id = current_user_id()
);"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Pretty CREATE TABLE formatting should format basic CREATE TABLE with pretty option enabled 1`] = `
"CREATE TABLE users (
id serial PRIMARY KEY,
name text NOT NULL,
email text UNIQUE
);"
`;

exports[`Pretty CREATE TABLE formatting should format complex CREATE TABLE with pretty option enabled 1`] = `
"CREATE TABLE orders (
id serial PRIMARY KEY,
user_id int NOT NULL,
total numeric(10, 2) CHECK (total > 0),
status varchar(20) DEFAULT 'pending',
created_at pg_catalog.timestamp DEFAULT now(),
FOREIGN KEY (user_id) REFERENCES users (id)
);"
`;

exports[`Pretty CREATE TABLE formatting should maintain single-line format for complex table when pretty disabled 1`] = `"CREATE TABLE orders (id serial PRIMARY KEY, user_id int NOT NULL, total numeric(10, 2) CHECK (total > 0), status varchar(20) DEFAULT 'pending', created_at pg_catalog.timestamp DEFAULT now(), FOREIGN KEY (user_id) REFERENCES users (id));"`;

exports[`Pretty CREATE TABLE formatting should maintain single-line format when pretty option disabled 1`] = `"CREATE TABLE users (id serial PRIMARY KEY, name text NOT NULL, email text UNIQUE);"`;

exports[`Pretty CREATE TABLE formatting should use custom newline and tab characters in pretty mode 1`] = `
"CREATE TABLE users (
id serial PRIMARY KEY,
name text NOT NULL,
email text UNIQUE
);"
`;
Loading