|
| 1 | +# pg-ast |
| 2 | + |
| 3 | +<p align="center" width="100%"> |
| 4 | + <img height="120" src="https://github.com/constructive-io/pgsql-parser/assets/545047/6440fa7d-918b-4a3b-8d1b-755d85de8bea" /> |
| 5 | +</p> |
| 6 | + |
| 7 | +<p align="center" width="100%"> |
| 8 | + <a href="https://github.com/constructive-io/pgsql-parser/actions/workflows/run-tests.yaml"> |
| 9 | + <img height="20" src="https://github.com/constructive-io/pgsql-parser/actions/workflows/run-tests.yaml/badge.svg" /> |
| 10 | + </a> |
| 11 | + <a href="https://github.com/constructive-io/pgsql-parser/blob/main/LICENSE-MIT"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a> |
| 12 | + <a href="https://www.npmjs.com/package/pg-ast"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/pgsql-parser?filename=packages%2Fpg-ast%2Fpackage.json"/></a> |
| 13 | +</p> |
| 14 | + |
| 15 | +`pg-ast` is a utility library for `@pgsql/types`, offering convenient functions to work with PostgreSQL Abstract Syntax Tree (AST) nodes in a type-safe manner. This library facilitates the creation of AST nodes for building SQL queries or statements programmatically. |
| 16 | + |
| 17 | +> **Note**: If you need the runtime schema for AST introspection, use [`@pgsql/utils`](https://www.npmjs.com/package/@pgsql/utils) instead. |
| 18 | +
|
| 19 | +# Table of Contents |
| 20 | + |
| 21 | +1. [pg-ast](#pg-ast) |
| 22 | + - [Features](#features) |
| 23 | +2. [Installation](#installation) |
| 24 | +3. [Usage](#usage) |
| 25 | + - [AST Node Creation](#ast-node-creation) |
| 26 | + - [JSON AST](#json-ast) |
| 27 | + - [Select Statement](#select-statement) |
| 28 | + - [Creating Table Schemas Dynamically](#creating-table-schemas-dynamically) |
| 29 | +4. [Related Projects](#related) |
| 30 | +5. [Disclaimer](#disclaimer) |
| 31 | + |
| 32 | +## Features |
| 33 | + |
| 34 | +- **AST Node Creation**: Simplifies the process of constructing PostgreSQL AST nodes, allowing for easy assembly of SQL queries or statements programmatically. |
| 35 | +- **Comprehensive Coverage**: Supports all node types defined in the PostgreSQL AST. |
| 36 | +- **Seamless Integration**: Designed to be used alongside the `@pgsql/types` package for a complete AST handling solution. |
| 37 | + |
| 38 | +## Installation |
| 39 | + |
| 40 | +To add `pg-ast` to your project, use the following npm command: |
| 41 | + |
| 42 | +```bash |
| 43 | +npm install pg-ast |
| 44 | +``` |
| 45 | + |
| 46 | +## Usage |
| 47 | + |
| 48 | +### AST Node Creation |
| 49 | + |
| 50 | +With the AST helper methods, creating complex SQL ASTs becomes straightforward and intuitive. |
| 51 | + |
| 52 | +#### JSON AST |
| 53 | + |
| 54 | +Explore the PostgreSQL Abstract Syntax Tree (AST) as JSON objects with ease using `pg-ast`. Below is an example of how you can generate a JSON AST using TypeScript: |
| 55 | + |
| 56 | +```ts |
| 57 | +import * as t from 'pg-ast'; |
| 58 | +import { SelectStmt } from '@pgsql/types'; |
| 59 | +import { deparse } from 'pgsql-deparser'; |
| 60 | + |
| 61 | +const selectStmt: { SelectStmt: SelectStmt } = t.nodes.selectStmt({ |
| 62 | + targetList: [ |
| 63 | + t.nodes.resTarget({ |
| 64 | + val: t.nodes.columnRef({ |
| 65 | + fields: [t.nodes.aStar()] |
| 66 | + }) |
| 67 | + }) |
| 68 | + ], |
| 69 | + fromClause: [ |
| 70 | + t.nodes.rangeVar({ |
| 71 | + relname: 'some_amazing_table', |
| 72 | + inh: true, |
| 73 | + relpersistence: 'p' |
| 74 | + }) |
| 75 | + ], |
| 76 | + limitOption: 'LIMIT_OPTION_DEFAULT', |
| 77 | + op: 'SETOP_NONE' |
| 78 | +}); |
| 79 | +console.log(selectStmt); |
| 80 | +// Output: { "SelectStmt": { "targetList": [ { "ResTarget": { "val": { "ColumnRef": { "fields": [ { "A_Star": {} } ] } } } } ], "fromClause": [ { "RangeVar": { "relname": "some_amazing_table", "inh": true, "relpersistence": "p" } } ], "limitOption": "LIMIT_OPTION_DEFAULT", "op": "SETOP_NONE" } } |
| 81 | +console.log(await deparse(stmt)) |
| 82 | +// Output: SELECT * FROM some_amazing_table |
| 83 | +``` |
| 84 | + |
| 85 | +#### Select Statement |
| 86 | + |
| 87 | +```ts |
| 88 | +import * as t from 'pg-ast'; |
| 89 | +import { SelectStmt } from '@pgsql/types'; |
| 90 | +import { deparse } from 'pgsql-deparser'; |
| 91 | + |
| 92 | +const query: { SelectStmt: SelectStmt } = t.nodes.selectStmt({ |
| 93 | + targetList: [ |
| 94 | + t.nodes.resTarget({ |
| 95 | + val: t.nodes.columnRef({ |
| 96 | + fields: [t.nodes.string({ sval: 'name' })] |
| 97 | + }) |
| 98 | + }), |
| 99 | + t.nodes.resTarget({ |
| 100 | + val: t.nodes.columnRef({ |
| 101 | + fields: [t.nodes.string({ sval: 'email' })] |
| 102 | + }) |
| 103 | + }) |
| 104 | + ], |
| 105 | + fromClause: [ |
| 106 | + t.nodes.rangeVar({ |
| 107 | + relname: 'users', |
| 108 | + inh: true, |
| 109 | + relpersistence: 'p' |
| 110 | + }) |
| 111 | + ], |
| 112 | + whereClause: t.nodes.aExpr({ |
| 113 | + kind: 'AEXPR_OP', |
| 114 | + name: [t.nodes.string({ sval: '>' })], |
| 115 | + lexpr: t.nodes.columnRef({ |
| 116 | + fields: [t.nodes.string({ sval: 'age' })] |
| 117 | + }), |
| 118 | + rexpr: t.nodes.aConst({ |
| 119 | + ival: t.ast.integer({ ival: 18 }) |
| 120 | + }) |
| 121 | + }), |
| 122 | + limitOption: 'LIMIT_OPTION_DEFAULT', |
| 123 | + op: 'SETOP_NONE' |
| 124 | +}); |
| 125 | + |
| 126 | +await deparse(createStmt); |
| 127 | +// SELECT name, email FROM users WHERE age > 18 |
| 128 | +``` |
| 129 | + |
| 130 | +#### Creating Table Schemas Dynamically |
| 131 | + |
| 132 | +```ts |
| 133 | +// Example JSON schema |
| 134 | +const schema = { |
| 135 | + "tableName": "users", |
| 136 | + "columns": [ |
| 137 | + { "name": "id", "type": "int", "constraints": ["PRIMARY KEY"] }, |
| 138 | + { "name": "username", "type": "text" }, |
| 139 | + { "name": "email", "type": "text", "constraints": ["UNIQUE"] }, |
| 140 | + { "name": "created_at", "type": "timestamp", "constraints": ["NOT NULL"] } |
| 141 | + ] |
| 142 | +}; |
| 143 | + |
| 144 | +// Construct the CREATE TABLE statement |
| 145 | +const createStmt = t.nodes.createStmt({ |
| 146 | + relation: t.ast.rangeVar({ |
| 147 | + relname: schema.tableName, |
| 148 | + inh: true, |
| 149 | + relpersistence: 'p' |
| 150 | + }), |
| 151 | + tableElts: schema.columns.map(column => t.nodes.columnDef({ |
| 152 | + colname: column.name, |
| 153 | + typeName: t.ast.typeName({ |
| 154 | + names: [t.nodes.string({ sval: column.type })] |
| 155 | + }), |
| 156 | + constraints: column.constraints?.map(constraint => |
| 157 | + t.nodes.constraint({ |
| 158 | + contype: constraint === "PRIMARY KEY" ? "CONSTR_PRIMARY" : constraint === "UNIQUE" ? "CONSTR_UNIQUE" : "CONSTR_NOTNULL" |
| 159 | + }) |
| 160 | + ) |
| 161 | + })) |
| 162 | +}); |
| 163 | + |
| 164 | +// `deparse` function converts AST to SQL string |
| 165 | +const sql = await deparse(createStmt, { pretty: true }); |
| 166 | + |
| 167 | +console.log(sql); |
| 168 | +// OUTPUT: |
| 169 | + |
| 170 | +// CREATE TABLE users ( |
| 171 | +// id int PRIMARY KEY, |
| 172 | +// username text, |
| 173 | +// email text UNIQUE, |
| 174 | +// created_at timestamp NOT NULL |
| 175 | +// ) |
| 176 | +``` |
0 commit comments