pj
Version:
Create SQL strings for PostgreSQL by interfacing with a javascript API
520 lines (391 loc) • 13.9 kB
Markdown
for values, columns, tables, operators, and functions
* Function aliasing within `select` clauses
* Implicit joins via `where` directives
This API is designed to allow you to programatically construct SQL queries for PostgreSQL fast and efficiently.
```javascript
var pj = require('pj');
// connect to database (uses node-postgres on node.js environments)
var db = new pj('blake@/project_db');
// define a relationship that joins two tables
db.relate('retailer.owned_by=owner.id');
// some values to exclude from our query
var exclude_states = ['Hawaii','Alaska'];
db.from('retailers')
.select('.name','location::geojson', 'email=owner.email_address')
.where({
country: 'USA',
state: pj('!=', exclude_states),
owner: {
name: pj('not like', '%o\'connor'),
phone: /^[0-9]{10}$/,
}
})
.order('name');
```
will generate SQL equivalent to:
```sql
select
"retailers"."name" as "name",
ST_AsGeoJSON("location") as "location",
"owner"."email_address" as "email"
from "retailers"
join "owner" on "retailer"."owned_by"="owner"."id"
where (
"country" = 'USA'
and ("state" != 'Hawaii' and "state" != 'Alaska')
and "owner"."name" not like '%o''connor'
and "owner"."phone" ~ '^[0-9]{10}$'
) order by "name" asc
```
You can create a new instance of `pj` to get a handle to your database:
```javascript
var retailers = new pj('postgres://user@host:5432/database');
// or...
var retailers = new pj({
user: 'user',
host: 'host',
port: 5432,
database: 'database',
});
```
You can also use the global `pj` as a handle to a single database by passing connection parameters to the `global` option in `pj.config`:
```javascript
pj.config({
global: {
user: 'blake',
host: 'localhost', // this is also by default
port: 5432, // this is also by default
database: 'my_db',
},
});
pj.from('my_table')
...
```
On the other hand, you may omit all arguments to the `pj` constructor if you only wish to generate query strings:
```javascript
var gen = pj();
```
```javascript
.select(field_1 [, field_2, ..., field_n)
```
Where `typeof field_` must be either `string` or `object`. The `string` version supports a strict syntax for specifying column names, creating an alias, aggregating rows, calling functions, using SQL operators, and type casting. The following section documents this syntax.
There are several different ways to access a field, each one has a specific purpose.
Using standard PostgreSQL identifier characters in the select-syntax string will yield a named identifier.
```javascript
.from('post').select('id')
```
yields:
```sql
select "id" from "post"
```
This can be useful for selecting aliases created elsewhere in the statement, but it is prone to ambiguation when multiple tables are joined together. To reference a column when there is more than one table present, you should use absolute references.
Prefixing the column name with a dot will produce an absolute reference to that column by using the name of the primary table specified in the `from` clause:
```javascript
.from('post').select('.id')
```
yiels:
```sql
select "post"."id" from "post"
```
It is typically a best practice to use absolute references when joining tables in order to prevent conflicts of columns with the same name from different tables:
```javascript
.from('post').select('post.id', 'user.id', 'user.name')
```
yields *(assuming the `post <-> user` table [relationship is defined](
```sql
select "post"."id", "user"."id", "user"."name" from "post" join "user" on "user"."id" = "post"."user_id"
```
Any field (including the outputs of functions) can be aliased by using the `=` operator:
```javascript
.from('post').select('post_id=post.id')
```
yields:
```sql
select "post"."id" as "post_id" from "post"
```
Aliasing the table specified in the `from` clause will also be reflected in the dotal-prefix syntax:
```javascript
.from('p=post').select('.id')
```
yields:
```sql
select "p"."id" from "post" as "p"
```
Any field (including the outputs of functions) can be cast into alternate data types by using the `::` operator.
#### Native Data Types
[PostgreSQL Data Types](http://www.postgresql.org/docs/9.4/static/datatype.html) that appear after the `::` operator will simply cast the preceding field's value to its type:
```javascript
.select('post.score::int')
```
yields:
```sql
select "post"."score"::int
```
Some native data types also accept arguments:
```javascript
.select('post.score::decimal(6,4)')
```
yields:
```sql
select "post"."score"::decimal(6,4)
```
#### Function Aliasing
You can define your own custom 'functions' by aliasing PostgreSQL functions using `pj.alias`:
```javascript
pj.alias(string aliasDef, string outputSub)
pj.alias(string aliasDef, function outputGen)
```
You may also define these aliases on a single `pj` instance. Doing this will prevent other instances created by the same `pj` object from holding conflicting aliases.
```javascript
.alias(string aliasDef, string outputSub)
.alias(string aliasDef, function outputGen)
```
These aliases are invoked (and perform their subsequent substitution) by using the `::` operator, in the same way that type-casting is done:
```javascript
pj.alias('excite', "concat($0, '!')");
db.from('fruit').select('color::excite');
```
yields:
```sql
select concat("color", '!') from "fruit"
```
##### Parameterized String Substitution
Passing a string into the `output` parameter will essentially substitue the field and any input arguments into the formatted string:
```javascript
// defines a custom function alias, globally
pj.alias('epoch_ms', 'extract(epoch from $0) * 1000');
db.select('time::epoch_ms').from('post')
```
yields:
```sql
select extract(epoch from "time") * 1000 as "time" from "post"
```
##### The $0 Parameter
Parameters/variables are prefixed with a `$` symbol. `$0` references the field preceding the `::` operator, although the above function can also be invoked like this:
```javascript
.select('epoch_ms(time)').from('post')
```
yields:
```sql
select extract(epoch from "time") * 1000 as "time" from "post"
```
##### Ordinally-named Parameters: Fields
When creating a custom function alias, if the parameter name is an integer then the subsequent replacement will yield the matching field name:
```javascript
pj.alias('describeItem($1)', "concat($1, ' is ', $0)");
db.from('fruit').select('describeItem(type, color)')
```
yields:
```sql
select concat("type", ' is ', "color") as "describeItem" from "fruit"
```
The only exception is if the argument is created using the [slash-notation](#slash-notation), which turns the argument into a single-quoted value.
##### Textually-named Parameters: Values
Using textually-named parameters will substitute escaped values into the string:
```javascript
// specifies an optional parameter where 'UTC' is set as the default value
pj.alias('epoch_ms($tz=UTC)', "extract(epoch from $0 at time zone $tz)");
db.select('time::epoch_ms').from('post'); // parenthesis are optional
db.select('time::epoch_ms()').from('post'); // will also use the default value
db.select('time::epoch_ms(PST)').from('post'); // note: argument is not enclosed with quotes
```
yields (respectively):
```sql
select extract(epoch from "time" at time zone 'UTC') as "time" from "post";
select extract(epoch from "time" at time zone 'UTC') as "time" from "post";
select extract(epoch from "time" at time zone 'PST') as "time" from "post";
```
If you wish to inject non-values (such as SQL keywords) into the query, you may prefix the variable name with `_`:
```javascript
pj.alias('addTime($_type,$value)', '$0 + $_type $value');
db.from('post').select('.time::addTime(interval, 3 hours)');
```
yields:
```sql
select "post"."time" + interval '3 hours'
```
Notice how the arguments to the call are not enclosed by any quotes. Arguments are terminated by the `)` character; for subtitution strings they are split by `,` delimiter. Custom function aliases that call `pj.alias` with a javascript function instead of a string however receive the entire text within the `(` `)` characters (ie: the text is not split). This allows the handling function to parse the arguments however it wants (albeit devoid of any right parenthesis characters).
##### TODO: document `pj.alias('eg', function(){})`
In some cases, you may need to opt for this style of passing arguments:
```javascript
.select([string alias,] array fields [, string casters])
```
This style allows you apply different functions to multiple fields, and then apply a function that accepts those expressions as inputs:
```javascript
db.select([
'trajectory::startPoint',
'trajectory::endPoint'
], '::distance');
```
yields:
```sql
select ST_Distance(
ST_StartPoint("trajectory"),
ST_EndPoint("trajectory")
) as "distance";
```
`startPoint`, `endPoint` and `distance` are PostGIS function aliases that come shipped with `pj`.
By default, `pj` defines several functions that alias [PostGIS Geometry Functions](http://postgis.net/docs/manual-2.1/reference.html) for constructing, accessing, editing, and outputting geometry data:
For example:
```javascript
.from('buildings').select('boundary::rotateX(90deg)::geojson')
```
yields:
```sql
select ST_AsGeoJSON(ST_RotateX("boundary", pi()*0.5)) as "boundary" from "building"
```
Here is a table that shows which functions are mapped to by their equivalent alias:
##### Geometry Constructors
| PostGIS Function | `pj` Alias Equivalent |
| ---------------- | -------------------- |
| ST_AsGeoJSON | geojson |
| ST_AsKML | kml |
| ST_AsText | wkt |
| ST_AsEWKT | ewkt |
| ST_AsEWKB | ewkb |
| ST_AsBinary | binary |
| ST_AsGeoHash | geohash |
| ST_AsSVG | svg |
| ST_AsLatLonText | latlon |
| | |
| ST_GeomFromText | wkt_geom |
| ST_GeomFromWKB | wkb_geom |
| ST_GeomFromEWKB | ewkb_geom |
| ST_GeomFromEWKT | ewkt_geom |
| ST_GeomFromGeoHash | geohash_geom |
| ST_GeomFromGML | gml_geom |
| ST_GeomFromGeoJSON | geojson_geom |
| ST_GeomFromKML | kml_geom |
| | |
| ST_GeogFromText | wkt_geog |
| ST_GeogFromWKB | wkb_geog |
| ST_GeogFromEWKB | ewkb_geog |
| ST_GeogFromEWKT | ewkt_geog |
| | |
##### Geometry Accessors
| PostGIS Function | `pj` Alias Equivalent |
| ---------------- | -------------------- |
| ST_X | x |
| ST_Y | y |
| ST_Z | z |
| ST_M | m |
| | |
| ST_NumPoints | numPoints |
| ST_NPoints | nPoints |
| ST_PointN | pointN |
| ST_StartPoint | startPoint |
| ST_EndPoint | endPoint |
| | |
| ST_NumInteriorRings | numInteriorRings |
| ST_NRings | nRings |
| ST_ExteriorRing | exteriorRing |
| ST_InteriorRingN | interiorRingN |
| | |
| ST_NumGeometries | numGeometries |
| ST_GeometryN | geometryN |
##### Geometry Editors
| PostGIS Function | `pj` Alias Equivalent |
| ---------------- | -------------------- |
| ST_Scale | scale |
| ST_Translate | translate |
| ST_Rotate | rotate |
| ST_RotateX | rotateX |
| ST_RotateY | rotateY |
| ST_RotateZ | rotateZ |
| ST_Affine | affine |
| ST_TransScale | transScale |
| | |
| ST_Transform | transform |
| ST_SetSRID | setSrid |
### <a name="slash-notation"> Selecting Literals (Slash-Notation)
To generate values (single-quoted literals) within the select clause, you can use this special slash-notation:
```javascript
.from().select('/This is a string/')
```
yields:
```sql
select 'This is a string'
```
Any single-quotes within the `/` delimiters are escaped:
```javascript
.from().select("/This is a 'string'/")
```
yields:
```sql
select 'This is a ''string'''
```
The first and last occurences of the `/` delimiter set the bounds for the value, so you do not need to worry about escaping extra `/` characters:
```javascript
var text = "Don't worry / I'm safe!";
db.from().select('output=/'+text+'/')
```
yields:
```sql
select 'Don''t worry / I''m safe!' as "output"
```
Since this notation only allows you to insert one single-quoted value per javascript string, you can exploit the `array fields` parameter of the `select` function to pass multiple single-quoted values to a function:
```javascript
db.alias('join($1)', "concat($0, ' ', $1)");
db.select('message=', [
'/hello,/',
'/world!/'
], '::join')
```
yields:
```sql
select concat('hello,', ' ', 'world!') as "message";
```
The slash-notation is convenient for constructing geometry with PostGIS:
```javascript
.select([
'/point(34.72 -118.25)/::wkt_geom',
'/point(36.12 -119.4)/::wkt_geom'
], '::distance')
```
yields:
```sql
select ST_Distance(
ST_GeomFromText('point(34.72 -118.25)'),
ST_GeomFromText('point(36.12 -119.4)')
) as "distance"
```
## <a name="implicit-joins"> Implicit Joins
You can take advantage of fixed relationships by declaring how two tables are related to one another.
```javascript
.relate(string relationship)
.relate(string tableA_dot_columnB, string tableX_dot_columnY)
```
For example:
```javascript
db.relate('post.user_id=user.id');
db.from('post').where({
user: {
name: 'jedi',
}
})
```
yields:
```sql
select * from "post"
join "user" on "post"."user_id" = "user"."id"
where "user"."name" = 'jedi';
```
```sh
$ npm install pj
```
* Parameterized queries