All Projects → oguimbal → pgsql-ast-parser

oguimbal / pgsql-ast-parser

Licence: other
Yet another simple Postgres SQL parser

Programming Languages

typescript
32286 projects
Nearley
35 projects
javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to pgsql-ast-parser

Ozo
OZO is a C++17 Boost.Asio based header-only library for asyncronous communication with PostgreSQL DBMS.
Stars: ✭ 138 (-9.21%)
Mutual labels:  postgres, pgsql
laravel-ltree
LTree Extension (PostgreSQL) for Laravel
Stars: ✭ 19 (-87.5%)
Mutual labels:  postgres, pgsql
postgres-deno
A PostgreSQL extension for Deno: run Typescript in PostgreSQL functions and triggers.
Stars: ✭ 87 (-42.76%)
Mutual labels:  postgres, deno
Blog
Everything about database,business.(Most for PostgreSQL).
Stars: ✭ 6,330 (+4064.47%)
Mutual labels:  postgres, pgsql
Postgraphile
GraphQL is a new way of communicating with your server. It eliminates the problems of over- and under-fetching, incorporates strong data types, has built-in introspection, documentation and deprecation capabilities, and is implemented in many programming languages. This all leads to gloriously low-latency user experiences, better developer experiences, and much increased productivity. Because of all this, GraphQL is typically used as a replacement for (or companion to) RESTful API services.
Stars: ✭ 10,967 (+7115.13%)
Mutual labels:  postgres, pgsql
Pgfe
The PostgreSQL client API in modern C++
Stars: ✭ 98 (-35.53%)
Mutual labels:  postgres, pgsql
Yiigo
🔥 Go 轻量级开发通用库 🚀🚀🚀
Stars: ✭ 304 (+100%)
Mutual labels:  postgres, pgsql
Vscode Postgres
PostgreSQL extension for vscode providing explorer, highlighting, diagnostics, and intellisense
Stars: ✭ 117 (-23.03%)
Mutual labels:  postgres, pgsql
phpPgAdmin6
PHP7+ Based administration tool for PostgreSQL 9.3+
Stars: ✭ 45 (-70.39%)
Mutual labels:  postgres, pgsql
coreutils
🦕 Cross-platform Deno rewrite of the GNU Coreutils
Stars: ✭ 22 (-85.53%)
Mutual labels:  deno
svelte-adapter-deno
A SvelteKit adapter for Deno
Stars: ✭ 152 (+0%)
Mutual labels:  deno
exodus.ts
The first MongoDB compatible data migration tool built on for the Deno runtime environment, allowing users to perform complete schema and database migrations.
Stars: ✭ 42 (-72.37%)
Mutual labels:  deno
integresql
IntegreSQL manages isolated PostgreSQL databases for your integration tests.
Stars: ✭ 475 (+212.5%)
Mutual labels:  postgres
google-bigtable-postgres-fdw
Google Bigtable Postgres FDW in Rust
Stars: ✭ 37 (-75.66%)
Mutual labels:  postgres
ts-transform-react-constant-elements
A TypeScript AST Transformer that can speed up reconciliation and reduce garbage collection pressure by hoisting React elements to the highest possible scope.
Stars: ✭ 44 (-71.05%)
Mutual labels:  ast
elephantry
PostgreSQL OMM for rust
Stars: ✭ 28 (-81.58%)
Mutual labels:  postgres
pg-dba-egitim
Eğitim Konuları
Stars: ✭ 23 (-84.87%)
Mutual labels:  postgres
pg-ipc
IPC over PostgreSQL LISTEN/NOTIFY/UNLISTEN exposed as an EventEmitter
Stars: ✭ 27 (-82.24%)
Mutual labels:  postgres
publish
Publish your module with one command in Deno.
Stars: ✭ 16 (-89.47%)
Mutual labels:  deno
Venflow
A brand new, fast and lightweight ORM, build for PostgreSQL.
Stars: ✭ 162 (+6.58%)
Mutual labels:  postgres

🏃‍♀️ pgsql-ast-parser is a Postgres SQL syntax parser. It produces a typed AST (Abstract Syntax Tree), covering the most common syntaxes of pgsql.

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 :)

📐 Installation

With NodeJS

npm i pgsql-ast-parser

With Deno

Just reference it like that:

import { /* imports here */ } from 'https://deno.land/x/pgsql_ast_parser/mod.ts';

📖 Parsing SQL

I strongly recommand NOT using this parser without Typescript. It will work, but types are awesome.

Parse sql to an AST like this:

import { parse, Statement } from 'pgsql-ast-parser';

// parse multiple statements
const ast: Statement[] = parse(`BEGIN TRANSACTION;
                                insert into my_table values (1, 'two')`);

// parse a single statement
const ast: Statement = parseFirst(`SELECT * FROM "my_table";`);

🔍 Inspecting SQL AST

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:

import { astVisitor, parse } from 'pgsql-ast-parser';

const tables = new Set();
let joins = 0;
const visitor = astVisitor(map => ({

    // implement here AST parts you want to hook

    tableRef: t => tables.add(t.name),
    join: t => {
        joins++;
        // call the default implementation of 'join'
        // this will ensure that the subtree is also traversed.
        map.super().join(t);
    }
}))

// start traversing a statement
visitor.statement(parseFirst(`select * from ta left join tb on ta.id=tb.id`));

// print result
console.log(`Used tables ${[...tables].join(', ')} with ${joins} joins !`)

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).

🖨 Converting an AST to SQL

That's super easy:

import { toSql } from 'pgsql-ast-parser';

const sql: string = toSql.statement(myAst);

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.

📝 Modifying SQL AST

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:

import { toSql, parseFirst, astMapper } from 'pgsql-ast-parser';

// create a mapper
const mapper = astMapper(map => ({
    tableRef: t => {
        if (t.name === 'foo') {
            return {
                 // Dont do that... see below
                 // (I wrote this like that for the sake of explainability)
                ...t,
                name: 'bar',
            }
        }

        // call the default implementation of 'tableRef'
        // this will ensure that the subtree is also traversed.
        return map.super().tableRef(t);
    }
}))

// parse + map + reconvert to sql
const modified = mapper.statement(parseFirst('select * from foo'));

console.log(toSql.statement(modified!)); //  =>  SELECT * FROM "bar"

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':

// create a mapper
const mapper = astMapper(map => ({
    ref: c => c.name === 'foo' ? null : c,
}))

// process sql
const result = mapper.statement(parseFirst('select foo, bar from test'));

// Prints: SELECT "bar" FROM "test"
console.log(toSql.statement(result!));

If no valid AST can be produced after having removed it, result will be null.

A note on 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:

    member(val: a.ExprMember) {
        const operand = someOperandTransformation(val.operand);
        if (!operand) {
            return null;
        }
        return {
            ...val,
            operand,
        }
    }

Prefer an implement that checks that nothing has changed, for instance by using the assignChanged() helper.

    member(val: a.ExprMember) {
        const operand = someOperandTransformation(val.operand);
        if (!operand) {
            return null;
        }
        return assignChanged(val, {
            operand,
        });
    }

It's pretty easy to implement. To deal with this kind optimization with arrays, there is a arrayNilMap() helper exposed:

const newArray = arrayNilMap(array, elem => transform(elem));
if (newArray === array) {
    // transform() has not changed any element in the array !
}

Parsing literal values

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)

FAQ

  • How to parse named parameters like :name ? 👉 See here (TLDR)
  • Can I get detailed a location for each AST node ? 👉 Yes. Pass the option {locationTracking: true} to parse(), and use the locationOf(node) function.
  • Can I get the comments that the parser has ignored ? 👉 Yes. Use parseWithComments() instead of parse()

Development

Pull requests are welcome :)

To start hacking this lib, you'll have to:

... 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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].