Gathering detailed insights and metrics for @gthb/pgsql-ast-parser
Gathering detailed insights and metrics for @gthb/pgsql-ast-parser
Gathering detailed insights and metrics for @gthb/pgsql-ast-parser
Gathering detailed insights and metrics for @gthb/pgsql-ast-parser
npm install @gthb/pgsql-ast-parser
Typescript
Module System
Node Version
NPM Version
75.4
Supply Chain
98
Quality
74.4
Maintenance
100
Vulnerability
100
License
TypeScript (89.33%)
Nearley (9.79%)
JavaScript (0.89%)
Total Downloads
298
Last Day
2
Last Week
3
Last Month
14
Last Year
104
322 Stars
328 Commits
46 Forks
7 Watchers
1 Branches
17 Contributors
Updated on Jun 27, 2025
Minified
Minified + Gzipped
Latest Version
10.5.2
Package Id
@gthb/pgsql-ast-parser@10.5.2
Unpacked Size
1.17 MB
Size
157.71 kB
File Count
104
NPM Version
8.13.1
Node Version
16.15.1
Cumulative downloads
Total Downloads
Last Day
0%
2
Compared to previous day
Last Week
50%
3
Compared to previous week
Last Month
40%
14
Compared to previous month
Last Year
40.5%
104
Compared to previous year
37
❤ It works both in node or in browser.
⚠ This parser does not support (yet) PL/pgSQL. It might not even cover some funky syntaxes.
❤ Open an issue if you find an bug or unsupported syntax !
🔗 This parser has been created to implement pg-mem, an in-memory postgres db emulator. 👉 play with it here
⭐ this repo if you like this package, it helps to motivate me :)
1npm i pgsql-ast-parser
Just reference it like that:
1import { /* imports here */ } from 'https://deno.land/x/pgsql_ast_parser/mod.ts';
⚠ I strongly recommand NOT using this parser without Typescript. It will work, but types are awesome.
Parse sql to an AST like this:
1import { parse, Statement } from 'pgsql-ast-parser'; 2 3// parse multiple statements 4const ast: Statement[] = parse(`BEGIN TRANSACTION; 5 insert into my_table values (1, 'two')`); 6 7// parse a single statement 8const ast: Statement = parseFirst(`SELECT * FROM "my_table";`);
Once you have parsed an AST, you might want to traverse it easily to know what's in it.
There is a helper for that: astVisitor.
Here is an example which lists all the tables used in a request, and which counts how many joins it contains:
1 2import { astVisitor, parse } from 'pgsql-ast-parser'; 3 4const tables = new Set(); 5let joins = 0; 6const visitor = astVisitor(map => ({ 7 8 // implement here AST parts you want to hook 9 10 tableRef: t => tables.add(t.name), 11 join: t => { 12 joins++; 13 // call the default implementation of 'join' 14 // this will ensure that the subtree is also traversed. 15 map.super().join(t); 16 } 17})) 18 19// start traversing a statement 20visitor.statement(parseFirst(`select * from ta left join tb on ta.id=tb.id`)); 21 22// print result 23console.log(`Used tables ${[...tables].join(', ')} with ${joins} joins !`) 24
You'll find that AST visitors (that's the name of this pattern) are quite flexible and powerful once you get used to them !
👉 Here is the implementation of toSql which uses an astVisitor to reconstitude SQL from an AST (see below).
That's super easy:
1import { toSql } from 'pgsql-ast-parser'; 2 3const sql: string = toSql.statement(myAst); 4
Like with astVisitor()
or astModifier()
, you can also convert subparts of AST to SQL (not necessarily a whole statement) by calling other methods of toSql.
There is a special kind of visitor, which I called astMapper, which allows you to traverse & modify ASTs on the fly.
For instance, you could rename a table in a request like this:
1import { toSql, parseFirst, astMapper } from 'pgsql-ast-parser'; 2 3// create a mapper 4const mapper = astMapper(map => ({ 5 tableRef: t => { 6 if (t.name === 'foo') { 7 return { 8 // Dont do that... see below 9 // (I wrote this like that for the sake of explainability) 10 ...t, 11 name: 'bar', 12 } 13 } 14 15 // call the default implementation of 'tableRef' 16 // this will ensure that the subtree is also traversed. 17 return map.super().tableRef(t); 18 } 19})) 20 21// parse + map + reconvert to sql 22const modified = mapper.statement(parseFirst('select * from foo')); 23 24console.log(toSql.statement(modified!)); // => SELECT * FROM "bar" 25
Good to know: If you use Typescript, return types will force you to return something compatible with a valid AST.
However, if you wish to remove a node from a tree, you can return null. For instance, this sample removes all references to column 'foo'
:
1// create a mapper 2const mapper = astMapper(map => ({ 3 ref: c => c.name === 'foo' ? null : c, 4})) 5 6// process sql 7const result = mapper.statement(parseFirst('select foo, bar from test')); 8 9// Prints: SELECT "bar" FROM "test" 10console.log(toSql.statement(result!));
If no valid AST can be produced after having removed it, result
will be null.
astMapper
performance:The AST default modifier tries to be as efficient as possible: It does not copy AST parts as long as they do not have changed.
If you wan to avoid unnecessary copies, try to return the original argument as much as possible when nothing has changed.
For instance, instead of writing this:
1 member(val: a.ExprMember) { 2 const operand = someOperandTransformation(val.operand); 3 if (!operand) { 4 return null; 5 } 6 return { 7 ...val, 8 operand, 9 } 10 }
Prefer an implement that checks that nothing has changed, for instance by using the assignChanged()
helper.
1 member(val: a.ExprMember) { 2 const operand = someOperandTransformation(val.operand); 3 if (!operand) { 4 return null; 5 } 6 return assignChanged(val, { 7 operand, 8 }); 9 }
It's pretty easy to implement.
To deal with this kind optimization with arrays, there is a arrayNilMap()
helper exposed:
1const newArray = arrayNilMap(array, elem => transform(elem)); 2if (newArray === array) { 3 // transform() has not changed any element in the array ! 4}
Postgres implements several literal syntaxes (string-to-something converters), whiches parsers are exposed as helper functions by this pgsql-ast-parser:
parseArrayLiteral()
parses arrays literals syntaxes (for instance {a,b,c}
)parseGeometricLiteral()
parses geometric types (for instance, things like (1,2)
or <(1,2),3>
)parseIntervalLiteral()
parses interval inputs literals (such as P1Y2DT1H
or 1 yr 2 days 1 hr
):name
? 👉 See here (TLDR){locationTracking: true}
to parse()
, and use the locationOf(node)
function.parseWithComments()
instead of parse()
Pull requests are welcome :)
To start hacking this lib, you'll have to:
npm start
... once done, tests should appear. HMR is on, which means that changes in your code are instantly propagated to unit tests. This allows for ultra fast development cycles (running tests takes less than 1 sec).
To debug tests: Just hit "run" (F5, or whatever)... vscode should attach the mocha worker. Then run the test you want to debug.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
SAST tool detected but not run on all commits
Details
Reason
Found 11/30 approved changesets -- score normalized to 3
Reason
dependency not pinned by hash detected -- score normalized to 1
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
license file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
41 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-06-30
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More