npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

query-weaver

v0.1.5

Published

SQL query builder using template string literal

Downloads

194

Readme

Query Weaver

Compose SQL statements safely by leveraging template string literals

Install

$ npm install query-weaver

Usage

As a SQL Builder

import { sql } from 'query-weaver';
import pg from 'pg';

const foo = 1, bar = 'Bar';
const query = sql`SELECT * FROM foobar WHERE foo = ${foo} AND bar = ${bar}`;

console.log(query.toString());
// SELECT * FROM foobar WHERE foo = '1' AND bar = 'Bar'

const db = new pg.Pool();
const { rows } = await db.query(query);

console.log(rows);
// [ { foo: 1, bar: 'Bar' } ]

console.log(JSON.stringify(query, null, 2));
// {
//   "text": "SELECT * FROM foobar WHERE foo = $1 AND bar = $2",
//   "values": [
//     1,
//     "Bar"
//   ],
//   "embed": "SELECT * FROM foobar WHERE foo = '1' AND bar = 'Bar'"
// }

db.end();

As you can see, the query is executed using placeholder on the database. This makes string-value concatenation safe. You can also get a string embed version of the query, so that you can debug the query easily.

As a Query Helper (with node-postgres for example)

import { withQueryHelper } from 'query-weaver';
import pg from 'pg';

const db = withQueryHelper(new pg.Pool());

const foo = 1, bar = 'Bar';
const { rows } =
  await db.query`SELECT * FROM foobar WHERE foo = ${foo} AND bar = ${bar}`;

console.log(rows);
// [ { foo: 1, bar: 'Bar' } ]

db.end(); // this call will be proxied to the original pg.Pool() instance

Almost the same as above, but you can directly pass the template string to the query function.

WHERE builder

WHERE_AND / WHERE_OR / AND / OR / WHERE (WHERE_AND alias)

import { sql, WHERE, OR } from 'query-weaver';

const a = 1, b = 'string', c = null, d = 5, e = false, f = [1, 2, 3, 4, 5];
console.log(
  String(sql`SELECT * FROM foobar ${WHERE({ a, b, c }, OR({ d, e }))}`)
);
// SELECT * FROM foobar WHERE ((a = '1') AND (b = 'string') AND (c IS NULL) AND (((d = '5') OR (e = false))))

const q = sql`SELECT * FROM foobar ${WHERE(
  {
    a: 10,
    b: 'string',
    c: sql`IS UNKNOWN`,
    d: sql`BETWEEN ${a} AND ${d}`,
  },
  'e IS NULL',
  sql`f = ANY (${f})`
)}`;
console.log(q.text);
// SELECT * FROM foobar WHERE ((a = $1) AND (b = $2) AND (c IS UNKNOWN) AND (d BETWEEN $3 AND $4) AND (e IS NULL) AND (f = ANY ($5)))

console.log(q.embed);
// SELECT * FROM foobar WHERE ((a = '10') AND (b = 'string') AND (c IS UNKNOWN) AND (d BETWEEN '1' AND '5') AND (e IS NULL) AND (f = ANY (ARRAY['1','2','3','4','5'])))

JSON builder

import pg from 'pg';
import { withQueryHelper, json } from 'query-weaver';

const db = withQueryHelper(new pg.Pool());

const id = 10;
const obj = { b: 'string', c: [1, 2, 'X'], d: { e: null, f: undefined } };

const row =
  await db.getRow`SELECT * FROM jsonb_to_record(${json`{ 'a': ${obj}, 'b': ${id} }`}) AS (a jsonb, b int);`;

console.log(row);
// {
//   a: { b: 'string', c: [ 1, 2, 'X' ], d: { e: null } },
//   b: 10,
// }

db.end();

VALUES builder

buildValues / sql.values

sql.values([[1, 2, 3], ...]);              // => VALUES (1, 2, 3), (...), ...
sql.values([{ a: 1, b: 2, c: 3 }], ...]);  // => VALUES (1, 2, 3), (...), ...

Key builder

buildKeys / sql.keys

sql.keys({ a: 1, b: 2, c: 3 });         // => (a, b, c)
sql.keys([{ a: 1, b: 2, c: 3 }, ...]);  // => (a, b, c)

Raw builder

raw

console.log(sql`SELECT * FROM foobar WHERE ${raw("bar LIKE '%something%'")}`);

Simple INSERT builder and executor

buildInsert / sql.insert builder, and insert executor

sql.insert(tableName, { ... fieldValuePairs });  // => sql`INSERT INTO ...`
db.insert(tableName, { ... fieldValuePairs });   // => db.query`INSERT INTO ...`

// bulk insert
sql.insert(tableName, [{ ... fieldValuePairs }, ... ]);  // => sql`INSERT INTO ... VALUES (...), (...), ...`
db.insert(tableName, [{ ... fieldValuePairs }, ... ]);   // => db.query`INSERT INTO ... VALUES (...), (...), ...`

Simple UPDATE builder and executor

buildUpdate / sql.update builder, and update executor

sql.update(tableName, { ...fieldValuePairs }, { ...whereCondition }); // => sql`UPDATE ...`
db.update(tableName, { ...fieldValuePairs }, { ...whereCondition }); // => db.query`UPDATE ...`

Simple DELETE builder and executor

buildDelete / sql.delete builder, and delete executor

sql.delete(tableName, { ...whereCondition }); // => sql`DELETE FROM ...`
db.delete(tableName, { ...whereCondition }); // => db.query`DELETE FROM ...`

API

As you can see, an object built and constructed by sql keyword behave like a simple object with the following properties; text, values, and embed.

Furthermore, the object also comes up with the following APIs;

  • append(...) / push(...)
    • append a raw SQL string or an object created by sql, to the query
  • join(glue = ', ') / prefix(prefix) / suffix(suffix) / empty(empty)
    • set glue/prefix/suffix/empty string for toString(), respectively
  • setSewingPattern(prefix, glue, suffix, empty)
    • set sewing pattern for toString() at once
  • toString(opts?)
    • constructs SQL string (by using sewing pattern and opts settings)

To create the object;

  • sql`template string with ${value}` / json`{"a": "b", "c": ${value}}`
    • creates a query object with values that will be automatically escaped
  • sql(value, ...) / json(value)
    • creates a query object from only values that will be automatically escaped

These APIs can be used, for example, to construct IN clause;

console.log(
  sql`SELECT * FROM foobar WHERE foo IN (${sql(1, 2, 3).join()})`.embed,
);
// SELECT * FROM foobar WHERE foo IN ('1', '2', '3')

const a = [1, 2, 3];
console.log(sql`SELECT * FROM foobar WHERE foo IN (${sql(...a).join()})`.text);
// SELECT * FROM foobar WHERE foo IN ($1, $2, $3)

Caveats

  • Only sql and json accepts a template string literal.
  • The actual SQL statement executed on the database may differ between [.text, .values] and .embed, due to differences in serialize functions. If you really want to get the exact same statement, you can try this for example:
import pgUtil from 'pg/lib/utils.js';
import pgEscape from 'pg-escape';

console.log(sql`SELECT ${[1, 2, 3, 4, 5]}`.embed);
// SELECT ARRAY['1','2','3','4','5']

console.log(sql`SELECT ${pgUtil.prepareValue([1, 2, 3, 4, 5])}`.embed);
// SELECT '{"1","2","3","4","5"}'

// or, pass a custom serialize function
console.log(
  sql`SELECT ${[1, 2, 3, 4, 5]}`.toString({
    valueFn: (x) => pgEscape.literal(JSON.stringify(x)),
  }),
);
// SELECT '[1,2,3,4,5]'

DEBUG

You can get access to the statements and results when using the Query Helper. I use the following code to record the session;

import zlib from 'node:zlib'

const db = withQueryHelper(new pg.Pool(), {
  onError: (ctx, error) => console.error(JSON.stringify({ error: zlib.gzipSync(JSON.stringify({ ... ctx, error })).toString('base64') })),
  beforeQuery: (ctx) => console.log(JSON.stringify({ query: zlib.gzipSync(JSON.stringify(ctx)).toString('base64') })),
  afterQuery: (ctx) => console.log(JSON.stringify({ result: zlib.gzipSync(JSON.stringify(ctx)).toString('base64') })),
});

After that, you'll see something like this on the console;

{"query":"H4sIAAAAAAAAA6tWKkmtKFGyUgp29XF1DlHQUnAL8vdVSMvPT0osUgj3cA1yBXEUbBVUDJV0lMoSc0pTi5Wsog1jdZRSc5NSU4jRqm6oDtRblFpcmlMC1FytlJyfm5uYh9ALks0vd84vzQM6xRDMAVlSrQTUDxYAmghU7AQka2NrawEDVej4tQAAAA=="}

Now you can extract the contents by executing the following command;

% echo '... base64part' | base64 -d | zcat | jq -r .

# or if you're using X11, select base64 part and then;
% xclip -o | base64 -d | zcat | jq -r .
{
  "text": "SELECT * FROM foobar WHERE foo = $1",
  "values": [
    1
  ],
  "embed": "SELECT * FROM foobar WHERE foo = '1'",
  "results": {
    "command": "SELECT",
    "rowCount": 1,
    "rows": [
      {
        "foo": 1,
        "bar": "Bar"
      }
    ]
  }
}

It is very handy to replay the session like this;

% xclip -o | base64 -d | zcat | jq -r .embed | psql
 foo | bar
-----+-----
   1 | Bar
(1 row)

# Or, you can make a shell-script named `xclip-query` then
% xclip-query .embed | psql

Query Helper with custom query handler

The final underlying query function can be changed by using query option. So you can use Prisma, TypeORM, or whatever you want if you write a query handler for them.

Here are examples for using it;

const db = withQueryHelper(new PrismaClient(), {
  query: QueryHelper.prisma,
});

console.log(await db.query`...`);
const db = withQueryHelper(new DataSource({ ... }), {
  query: QueryHelper.typeorm,
});

console.log(await db.query`...`);
const queryable = async function (this: object, { text, values}: QueryConfig) {
  return {
    rows: [this], // `this` would be the object you passed to the constructor
    rowCount: 1,
  }
}

const db = withQueryHelper({ some: 'Hello, object' }, {
  query: queryable,
});

console.log(await db.getRow`HELLO QUERY`);
// { some: 'Hello, object' }

That's it! Now you can use Query Weaver interfaces on them.