Table Of Contents

Project Links

Brought to you by:

Gesundheit!

Gesundheit generates SQL, it does this using a sugary API for managing the abstract syntax tree of a SQL statement. After building your statement programmatically, gesundheit can compile it to a string or execute it against your database for you, using proper bound parameters and allowing for streaming of results.

Contents:

API summary

There are a few subsystems that make up gesundheit, but the majority of use cases will be covered by using the following properties of the main module:

gesundheit.{Select, SELECT, select}
Function for creating new SelectQuery instances.
gesundheit.{Update, UPDATE, update}
Function for creating new UpdateQuery instances.
gesundheit.{Delete, DELETE, delete}
Function for creating new DeleteQuery instances.
gesundheit.{Insert, INSERT, insert}
Function for creating new InsertQuery instances.
gesundheit.engines.{mysql, postgres}
Functions for creating new engines.
gesundheit.defaultEngine
The engine that will be used for queries that aren’t explicitly bound. This is set to a no-op engine that you will want to replace either using the gesundheit.engines functions or by implementing the engine interface yourself.
Join types
Constant nodes for use with SUDQuery.join. ‘LEFT’, ‘RIGHT’, ‘INNER’, ‘LEFT_OUTER’, ‘RIGHT_OUTER’, ‘FULL_OUTER’ ‘NATURAL’, ‘CROSS’
AST helper functions

These come from the nodes module and are often useful when constructing queries that the query manager classes don’t cover as well:

If you are implementing support for a different database engine or constructing particularly unusual SQL statements, you might also want to make use of these:

gesundheit.nodes
The nodes <Nodes> module.
gesundheit.dialects
The dialects <Dialects> module.

Introduction - Making queries

The main interface for building queries with gesundheit are the query manager classes. They provide an API designed to make most query building operations concise and fluent, while under the hood they manage an abstract syntax tree for the query.

Creating a query manager

All of the query managers are created with functions named after the query type that take a table (or alias) as their first parameter. To demonstrate we will create a simple select query:

select = require('gesundheit').select
departments = select('departments')

This creates a new SelectQuery query instance that generates the SQL string SELECT * FROM departments. To refine the field list we call SelectQuery.fields:

departments.fields('name', 'manager_id')

It’s important to note that all of the query manager methods modify the query in-place [1] so departments will now render to SELECT name, manager_id FROM departments.

Compiling

To turn the query object into a SQL string and array of bind parameters, we .compile the query:

assert.deepEqual(
  departments.compile(),
  [ 'SELECT name, manager_id FROM departments', [] ]
)

(there are no bind parameters in our query yet)

Most often you don’t really care about the SQL string and params themselves, but want result of performing the query on an actual database. In that case you simply use the .execute method:

query.execute (err, res) -> console.log {err, res}

... but gesundheit can’t know about how to find and connect to your database all on it’s own! To execute with a real connection you will need to bind the query object to an engine or client. You can bind a query by passing an engine/client as the first parameter to those methods that require a binding, or assigning a new defaultEngine for implicit binding.

The first step in either case is to create an engine:

gesundheit = require('gesundheit')

# The parameter to mysql() is the same as for mysql.createClient()
engine = gesundheit.engines.mysql({database: 'test'})

Then we can pass it, or a client we returned by it’s .connect method, to query.execute:

query.execute engine, (err, res) -> console.log {err, res}

engine.connect (err, client) ->
  throw err if err
  query.execute client, (err, res) -> console.log {err, res}

Finally, you can also set a new defaultEngine for implicit binding:

gesundheit.defaultEngine = engine
query.execute (err, res) -> console.log {err, res}

Now BaseQuery.execute and other methods that need a database client will get one from the engine automatically.

Aliasing tables and fields

Any function that accepts a table or field parameter will accept a string, an instance of the appropriate AST node type, or an alias object. Alias objects are objects with a single key-value pair where the key is an alias name and the value is the object to be aliased. So the alias object {p: 'people'} will generate the SQL string people AS p. Here is an example of aliasing table and field names:

# SELECT manager_id AS m_id FROM departments AS d;
select({d: 'departments'}, [{m_id: 'manager_id'}])

(This example also shows passing a list of fields to select as the second parameter).

Footnotes

[1]Use BaseQuery.copy if you want to generate multiple independent refinements from a single query instance.

Query Building API reference

The query manager classes use the following inheritance hierarchy:

  • BaseQuery
    • InsertQuery
    • SUDQuery
      • SelectQuery
      • UpdateQuery
      • DeleteQuery

The following functions for creating *Query class instances are re-exported by the main gesundheit module:

function exports.insert(tbl, fields, opts)

Create a new InsertQuery that will add rows to table.

The fields parameter is required to be an array of column names that will be inserted.

function exports.select(table, fields, opts)

Create a new SelectQuery selecting from table.

Parameters:
  • table – Table name to select rows from.
  • fields – (Optional) Fields to project from table. If this is not given, all fields (*) will be projected until SelectQuery.fields` is called.
  • opts – Additional options for BaseQuery.constructor
function exports.update(table, opts)

Create a new UpdateQuery that will update table.

function exports.delete(table, opts)

Create a new DeleteQuery that will delete rows from table.

BaseQuery

class BaseQuery

The base class for all queries. While this class itself is not part of gesundheits public API, the methods defined on it are.

method BaseQuery.constructor(opts)
Parameters:
  • opts.table – a String, Relation, Alias, or an object literal with a single key and value which will be interpreted as an alias name and table, respectively. This is given to as the first parameter to the query creation functions in queries/index
  • opts.bind – (optional) an engine or connection that the query will be bound to. The engine is used to render and/or execute the query. If not given gesundheit.defaultEngine will be used.
method BaseQuery.copy()

Instantiate a new query with a deep copy of this ones AST

method BaseQuery.visit(fn)

Call the given function in the context of this query. This is mostly useful in coffeescript where you can use it as a sort-of-DSL:

queryObject.visit ->
  @where x: val
  @orderBy x: 'ASC'

The current query is also given as the first parameter to the query in case you need it.

method BaseQuery.echo()

If called before .render(), then resulting SQL will be sent to stdout via console.log()

method BaseQuery.bind(bindable)

Bind this query object to a bindable object (engine or client). If no argument is given the query will be bound to the default engine.

method BaseQuery.render()

Render the query to SQL.

Parameters:
  • bindable – (optional) If present, the query will be bound to this object using bind
method BaseQuery.compile()

Compile this query object, returning a SQL string and parameter array.

Parameters:
  • bindable – (optional) If present, the query will be bound to this object using bind
method BaseQuery.execute(cb)

Execute the query and buffer all results.

Parameters:
  • bindable – (optional) If present, the query will be bound to this object using bind
  • cb – A node-style callback that will be called with any errors and/or the query results.
method BaseQuery.stream(cb)

Execute the query and stream the results.

Parameters:
  • bindable – (optional) If present, the query will be bound to this object using bind
  • cb – A node-style callback that will be called with any errors and/or each row of the query results.
function withBinding(original)

Decorates a method so that it can accept a bindable object as it’s first argument, and will always call @bind() before the method itself.

Insert

class InsertQuery extends BaseQuery

Insert queries are much simpler than most query types: they cannot join multiple tables.

method InsertQuery.addRows(rows...)

Add multiple rows of data to the insert statement.

method InsertQuery.addRow(row)

Add a single row

method InsertQuery.from(query)

Insert from a select query.

SUDQuery

class SUDQuery extends BaseQuery

SUDQuery is the base class for SELECT, UPDATE, and DELETE queries. It adds logic to BaseQuery for dealing with WHERE clauses and ordering.

method SUDQuery.where(alias, predicate)

Add a WHERE clause to the query. Can optionally take a table/alias name as the first parameter, otherwise the clause is added using the last table added to the query.

The where clause itself can be a comparison node, such as those produced by the ComparableMixin methods:

q.where(q.project('table','field1').eq(42))
q.where(q.project('table','field2').gt(42))

... Or an object literal where each key is a field name (or field name alias) and each value is a constraint:

q.where('table', {field1: 42, field2: {gt: 42}})

Constraints values can also be other projected fields:

p = q.project.bind(q, 'table')
q.where('table', p('field1').gt(p('field2')))
method SUDQuery.or(args...)

Add one or more WHERE clauses, all joined by the OR operator.

method SUDQuery.order(args...)

Add an ORDER BY to the query. Currently this always uses the “active” table of the query. (See SelectQuery.from)

Each ordering can either be a string, in which case it must be a valid-ish SQL snippet like ‘some_field DESC’, (the field name and direction will still be normalized) or an object, in which case each key will be treated as a field and each value as a direction.

method SUDQuery.limit(l)

Set the LIMIT on this query

method SUDQuery.offset(l)

Set the OFFSET of this query

Select

Examples

Start a select query with exports.select:

light_recliners = select('chairs', ['chair_type', 'size'])
  .where({chair_type: 'recliner', weight: {lt: 25}})

Join another table with SelectQuery.join:

men_with_light_recliners = light_recliners.copy()
  .join("people", {
    on: {chair_id: query.project('chairs', 'id')},
    fields: ['name']
  })
  .where({gender: 'M'})

Note that joining a table “focuses” it, so “gender” in .where({gender: 'M'}) refers to the people.gender column. To add more conditions on an earlier table refocus it with SelectQuery.focus:

men_with_light_recliners.focus('chairs')

Ordering and limits are added with methods of the same name:

men_with_light_recliners
  .order(weight: 'ASC)
  .limit(5)

The entire query can also be written using BaseQuery.visit (and less punctuation) like so:

men_with_light_recliners = select('chairs', ['chair_type', 'size']).visit ->
  @where chair_type: 'recliner', weight: {lt: 25}
  @join "people",
    on: {chair_id: @project('chairs', 'id')},
    fields: ['name']
  @where gender: 'M'
  @focus 'chairs'
  @order weight: 'ASC
  @limit 5

API

class SelectQuery extends SUDQuery

Adds a number of SELECT-specific methods to SUDQuery, such as fields and groupBy

method SelectQuery.fields(fields...)

Adds one or more fields to the query. If the second argument is an array, the first argument is treated as a table (in the same way that join understands tables) and the second argument as the list of fields to select/update from that table. The table must already be joined for this to work.

If the second argument is not an array, then each argument is treated as an individual field to be projected from the currently focused table.

method SelectQuery.agg(fun, fields...)

Adds one or more aggregated fields to the query

Parameters:
  • fun – name of SQL aggregation function.
  • fields – Fields to be projected from the current table and passed as arguments to fun

Example:

select('t1').agg('count', 'id') # SELECT count(id) FROM t1
method SelectQuery.distinct(bool)

Make this query DISTINCT on all fields.

method SelectQuery.join(table, opts)

Join another table to the query.

Parameters:
  • table – A table name, or alias literal. An error will be thrown if the table/alias name is not unique. See toRelation for more information on the many things table could be.
  • opts.on – An object literal expressing join conditions. See where for more.
  • opts.type – A join type constant (e.g. INNER, OUTER)
  • opts.fields – A list of fields to be projected from the newly joined table.
method SelectQuery.ensureJoin(table, opts)

The same as join, but will only join tbl if it is not joined already.

method SelectQuery.rel(alias)

A shorthand way to get a relation by (alias) name

method SelectQuery.project(alias, field)

Return a Projection node representing <alias>.<field>.

This node has a number methods from ComparableMixin that can create new comparison nodes usable in join conditions and where clauses:

# Find developers over the age of 45
s = select('people', ['name'])
dep_id = s.project('people', 'department_id')
s.join('departments', on: {id: dep_id})
s.where(s.project('departments', 'name').eq('development'))
s.where(s.project('people', 'age').gte(45))
method SelectQuery.focus(alias)

Make a different table “focused”, this will use that table as the default for the fields, order and where methods.

Parameters:
  • alias – The table/alias name to focus. If the table or alias is not already part of the query an error will be thrown.
method SelectQuery.groupBy(fields...)

Add a GROUP BY to the query.

Update

Examples

Updating rows that match a condition:

update('tweeters')            # UPDATE tweeters
  .set(influential: true)     # SET tweeters.influential = true
  .where(followers: gt: 1000) # WHERE tweeters.followers > 1000;
  .execute (err, res) ->
    throw err if err
    # Woohoo

API

class UpdateQuery extends SUDQuery

The update query is a little underpowered right now, and can only handle simple updates of a single table.

method UpdateQuery.set(data)

Add fields to the SET portion of this query.

Parameters:
  • data – An object mapping fields to values. The values will be passed to toParam to be converted into bound paramaeters.
method UpdateQuery.setNodes(nodes...)

Directly push one or more nodes into the SET portion of this query

Delete

Examples

Delete all rows that match a condition:

# DELETE FROM tweeters WHERE tweeters.followers < 10
delete('tweeters').where(followers: lt: 10)

API

class DeleteQuery extends SUDQuery

Delete queries don’t add any new methods to SUDQuery

Engines and Binding

A gesundheit query must be “bound” to an “engine” to render and/or execute. For apps that deal with a single database, you can simply create an engine instance during application startup, assign it to gesundheit.defaultEngine and not have to think about binding after that.

For more complicated scenarios where you need control over the exact connections used (e.g. transactions) you will need to understand the engine/binding system.

Engines

An engine is any object that implements the following API:

render(query)
Render the given query instance to a SQL string. This method must be synchronous, and will usually just delegate to a subclass of BaseDialect.
connect(callback)
Call callback(err, client) where client is an object with a query method that works the same as those of the pg and mysql driver clients. The client must also have an .engine property that points back to the engine instance that created it.
stream([client,] query, cb)
If client is not given, get one by calling connect. Then execute the query, calling cb(err, row) for each row in the result.
execute([client,] query, cb)
If client is not given, get one by calling connect. Then execute the query, calling cb(err, result) with the full query results.

Gesundheit exports factory functions for creating postgres and mysql engines:

function postgres(dsn)

Create a new Postgres engine using a DSN compatible with require('pg').connect(dsn). This generally has the from:

<scheme>://<user>@<host>[:<port>]/<database>

This will require('pg') and attempt to use the ‘native’ interface if it’s available, so that module must be installed for this to work.

function mysql(opts)

Create a new MySQL engine using an object that is compatible with require('node-mysql').createClient(opts),

Additionally, you can specify extra options for generic-pool by including them as an object in opts.pool. The create and destroy pool functions will be created for you.

This will require('mysql') and require('generic-pool') so those modules must be installed and loadable for this function to work.

function fakeEngine()

Create a no-op engine that simply returns the compiled SQL and parameter array to the result callback. This will be the default until you over-ride with gesundheit.defaultEngine = myAppEngine.

function withClient(original)

Decorate a method so that it will be called with a connected client prepended to the argument list. The method must receive an object bound to an engine as it’s first argument.

Bindings

The render, compile, stream, and execute methods of BaseQuery all require an engine to do their work. Rather than requiring the engine to be passed to each of these methods, the query can be “bound” to an engine or client object.

Queries are bound to such “bindable” objects in one of 3 ways:

  1. Using BaseQuery.bind.
  2. A bindable object can be given as the first parameter to methods that require the query to be bound (e.g. BaseQuery.execute).
  3. If a method that requires a binding is called on an unbound query (and no bindable is given) the value of gesundheit.defaultEngine will be used.

Dialects

Dialects are responsible for rendering an AST to a SQL string compatible with a particular DBMS. They are rarely used directly, instead a query is usually bound to an engine that will delegate rendering to a private dialect instance.

class BaseDialect

A dialect that isn’t specific to a particular DBMS, but used as a base for both MySQL and Postgres.

class MySQL extends BaseDialect

Specialization of BaseDialect for MySQL

Nodes

These are the classes that represent nodes in the AST for a SQL statement. Application code should very rarely have to deal with these classes directly; Instead, the APIs exposed by the various query manager classes are intended to cover the majority of use-cases. However, in the spirit of “making hard things possible”, the various AST nodes can be constructed and assembled manually if you so desire.

class ValueNode extends Node

A ValueNode is a literal string that should be printed unescaped.

class NodeSet extends Node

A set of nodes joined together by @glue

method NodeSet.constructor(@nodes, glue)
Parameters:
  • @nodes – A list of child nodes.
  • glue – A string that will be used to join the nodes when rendering
method NodeSet.copy()

Make a deep copy of this node and it’s children

method NodeSet.addNode(node)

Add a new Node to the end of this set

method NodeSet.params()

Recurse over nested NodeSet instances, collecting parameter values.

class FixedNodeSet extends NodeSet

A NodeSet that disables the addNode method after construction.

class FixedNamedNodeSet extends FixedNodeSet

A FixedNodeSet that instantiates a set of nodes defined by the class member @structure when it it instantiated.

See Select for example.

class SqlFunction extends Node

Includes ComparableMixin

class Alias extends Node

Example:

table = new Relation(‘my_table_with_a_long_name’) alias = new Alias(table, ‘mtwaln’)

class Relation extends ValueNode

A relation node represents a table name in a statement.

method Relation.ref()

Return the table name. This is a common interface with nodes:Alias.

method Relation.project(field)

Return a new Projection of field from this table.

method Relation.field(field)

An alias for Relation.project.

class Tuple extends ParenthesizedNodeSet

A tuple node. e.g. (col1, col2)

class ProjectionSet extends NodeSet

The list of projected columns in a query

class Projection extends FixedNodeSet

Includes ComparableMixin

class RelationSet extends NodeSet

Manages a set of relation and exposes methods to find them by alias.

class ComparableMixin

A mixin that adds comparison methods to a class. Each of these comparison methods will yield a new AST node comparing the invocant to the argument.

method ComparableMixin.eq(other)

this = other

method ComparableMixin.ne(other)

this <> other

method ComparableMixin.gt(other)

this > other

method ComparableMixin.lt(other)

this < other

method ComparableMixin.lte(other)

this <= other

method ComparableMixin.gte(other)

this >= other

method ComparableMixin.compare(op, other)

this op other

function toParam(it)

Return a Node that can be used as a parameter.

  • SelectQuery instances will be treated as un-named sub queries,
  • Node instances will be returned unchanged.
  • Arrays will be turned into a Tuple instance.

All other types will be wrapped in a Parameter instance.

function sqlFunction(name, args)

Create a new SQL function call node. For example:

count = sqlFunction(‘count’, new ValueNode(‘*’))

function getAlias(o)

Check if o is an object literal representing an alias, and return the alias name if it is.

function text(rawSQL, params...)

Construct a node with a raw SQL string and (optionally) parameters.

Parameter arguments are assumed to be values for placeholders in the raw string. Be careful: the number and types of these parameters will not be checked, so it is very easy to create invalid statements with this.

function binaryOp(left, op, right)

Create a new Binary node:

binaryOp('hstore_column', '->', toParam(y))
# hstore_column -> ?

See also

SelectQuery.project
Returns Projection objects that have comparison methods from ComparableMixin.
function copy(it)

Return a deep copy of it.