Skip to content

Commit

Permalink
finished support for new variable syntax.
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaly-t committed May 16, 2015
1 parent 6e623a7 commit 5a337e0
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 29 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ db.connect()
.then(function(obj){
sco = obj; // save the connection object;
// find active users created before today:
return sco.query("select * from users where active=$1 and created < $2::date",
return sco.query("select * from users where active=$1 and created < $2",
[true, new Date()]);
})
.then(function(data){
Expand Down Expand Up @@ -223,7 +223,8 @@ A detached transaction acquires a connection and exposes object `t` to let all c

#### Shared-connection Transactions

When executing a transaction within a shared connection chain, parameter `t` represents the same connection as `sco` from opening a shared connection, so either one can be used inside such a transaction interchangeably.
When executing a transaction within a shared connection chain, parameter `t` represents the same connection as `sco` from opening a shared connection,
so either one can be used inside such a transaction interchangeably.

```javascript
var promise = require('promise'); // or any other supported promise library;
Expand Down Expand Up @@ -433,7 +434,7 @@ function query(query, values, qrm);
* `query` (required) - a string with support for three types of formatting, depending on the `values` passed:
- format `$1` (single variable), if `values` is of type `string`, `boolean`, `number`, `Date`, `function` or `null`;
- format `$1, $2, etc..`, if `values` is an array of values;
- format `${propName}`, if `values` is an object (not null and not Date);
- format `${propName}` or `$(propName)`, if `values` is an object (not null and not Date);
* `values` (optional) - value/array/object to replace the variables in the query;
* `qrm` - (optional) *Query Result Mask*, as explained below...
Expand All @@ -458,7 +459,7 @@ When a value/property inside array/object is of type `object` (except for `null`
serialized into JSON, the same as calling method `as.json()`, except the latter would convert anything to JSON.

Raw text values can be injected by using variable name appended with symbol `^`:
`$1^, $2^, etc...` or `${varName^}`.
`$1^, $2^, etc...`, `${varName^}` or `$(varName)`.
Raw text is injected without any pre-processing, which means:
* No replacing each single-quote symbol `'` with two;
* No wrapping text into single quotes.
Expand Down Expand Up @@ -557,6 +558,9 @@ of letters, digits, underscores and `$`;
It is important to know that while property values `null` and `undefined` are both formatted as `null`,
an error is thrown when the property doesn't exist at all.

Version 1.2.0 of the library extended the syntax to also support `$(propName)`, for better
compliance with ES6.

## Functions and Procedures
In PostgreSQL stored procedures are just functions that usually do not return anything.

Expand Down Expand Up @@ -635,8 +639,9 @@ return text is to be without any pre-processing:
* No replacing each single-quote symbol `'` with two;
* No wrapping text into single quotes;
* Throwing an error when the variable value is `null` or `undefined`.

This adheres to the query formatting, as well as method `as.format` when variable
names are appended with symbol `^`: `$1^, $2^, etc...` or `${varName^}`.
names are appended with symbol `^`: `$1^, $2^, etc...`, `${varName^}` or `$(varName^)`.

As none of these helpers are associated with any database, they can be used from anywhere.

Expand Down Expand Up @@ -693,11 +698,12 @@ If you want to get the most out the query-related events, you should use [pg-mon
#### pgFormatting

By default, **pg-promise** provides its own implementation of the query formatting,
supporting two different formats:
supporting the following formats:

* Format `$1, $2, etc`, when parameter `values` is either a single value or an array of values;
* Format `${propName}` - ES6-like format, if `values` is an object that's not `null`
and not a `Date` instance.
* Format `${propName}` - ES6-like format, if `values` is an object that's not `null` and not a `Date` instance.

The latter received syntax extension in version 1.2.0 to also support `$(propName)`, for better ES6 compliance.

Every query method of the library accepts `values` as its second parameter.

Expand Down Expand Up @@ -971,6 +977,7 @@ If you do not call it, your process may be waiting for 30 seconds (default) or s

# History

* Version 1.2.0 extended [Named Parameters](#named-parameters) syntax with `$(varName)`. Released: May 16, 2015.
* Version 1.1.0 added support for functions as parameters. Released: April 3, 2015.
* Version 1.0.5 added strict query sequencing for transactions. Released: April 26, 2015.
* Version 1.0.3 added method `queryRaw(query, values)`. Released: April 19, 2015.
Expand Down
22 changes: 11 additions & 11 deletions lib/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ function formatCSV(values) {
///////////////////////////////////////////////////////////////
// Returns an array of unique variable names from a text string;
//
// Variables are defined using syntax "${varName}", with
// optional ${varName^}, for injecting raw text values.
// Variables are defined using syntax ${varName} or $(varName), with
// optional ${varName^} or $(varName^), for injecting raw text values.
//
// Variables follow javascript naming convention:
// - a valid variable starts with a letter, underscore or '$' symbol,
Expand All @@ -84,8 +84,8 @@ function enumVars(txt) {
// query formatting helper;
var formatAs = {

// Replaces each "${propName}" with the corresponding property value;
// Each ${propName^} is replaced with raw-text value.
// Replaces each ${propName} and $(propName) with the corresponding property value;
// Each ${propName^} and $(propName^) are replaced with their raw-text values.
object: function (query, obj) {
var names = enumVars(query); // extract all variables;
for (var i = 0; i < names.length; i++) {
Expand All @@ -99,16 +99,16 @@ var formatAs = {
var prop = obj[sName]; // property value;
var v = formatValue(prop, raw, obj);
// replacing special symbols '$' and '^' with '\$' and '\^',
// so they can be used within a replacement Reg-Ex further;
// so they can be used with RegExp further;
var map = {'$': '\\$', '^': '\\^'};
name = name.replace(/[\$\^]/g, function (k) {
return map[k];
});
if (!raw) {
name += "(?!\\^)"; // name not followed by ^
}
// Replacing all occurrences of the 'name' variable with the 'value';
query = query.replace(new RegExp("\\$\\{\\s*" + name + "\\s*}", 'g'), v);
// Replacing all occurrences of the 'name' variable with its 'value';
query = query.replace(new RegExp("\\$\\{\\s*" + name + "\\s*}|\\$\\(\\s*" + name + "\\s*\\)", 'g'), v);
}
return query;
},
Expand Down Expand Up @@ -136,7 +136,7 @@ var formatAs = {
}
return query;
},

// Replaces each occurrence of "$1" with the value passed;
// Each "$1^" is replaced with its raw-text equivalent.
value: function (query, value) {
Expand Down Expand Up @@ -180,9 +180,9 @@ function checkNullRaw(raw) {
//
// 1. format "$1, $2, etc", when 'values' is of type string, boolean, number, date, function or null
// (or an array of the same types, plus undefined values);
// 2. format ${propName}, when 'values' is an object (not null and not Date)
// 2. format ${propName} or $(propName), when 'values' is an object (not null and not Date)
//
// Raw text values can be injected using syntax: $1^,$2^ and ${propName^};
// Raw text values can be injected using syntax: "$1^,$2^,...", "${propName^}" or "$(propName^)";
//
// When formatting fails, the function throws an error.
function $formatQuery(query, values) {
Expand All @@ -194,7 +194,7 @@ function $formatQuery(query, values) {
query = formatAs.array(query, values);
} else {
if (values && typeof(values) === 'object' && !(values instanceof Date)) {
// "${propName}" formatting to be applied;
// ${propName} / $(propName) formatting to be applied;
query = formatAs.object(query, values);
} else {
if (values !== undefined) {
Expand Down
2 changes: 1 addition & 1 deletion test.bat
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@ECHO OFF

REM Use this batch to run tests on Windows.
REM NOTE: Make sure to install the packages first, using 'npm install'
REM NOTE: Make sure to follow all the Testing steps first: https://github.com/vitaly-t/pg-promise#testing

node_modules/.bin/jasmine-node.cmd test
53 changes: 44 additions & 9 deletions test/formatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ describe("Method as.number", function () {
it("must correctly reject invalid numbers", function () {

var err = " is not a number.";
expect(function(){
expect(function () {
pgp.as.number('');
}).toThrow("''" + err);

expect(function(){
pgp.as.number([1,2]);
expect(function () {
pgp.as.number([1, 2]);
}).toThrow("'1,2'" + err);

expect(function(){
expect(function () {
pgp.as.number(func);
}).toThrow("'" + func + "'" + err);

Expand Down Expand Up @@ -174,7 +174,7 @@ describe("Method as.csv", function () {

expect(pgp.as.csv()).toBe(""); // test undefined;
expect(pgp.as.csv([])).toBe(""); // test empty array;

expect(pgp.as.csv([[]])).toBe("array[]"); // test empty array;
expect(pgp.as.csv(null)).toBe("null"); // test null;
expect(pgp.as.csv([null])).toBe("null"); // test null in array;
expect(pgp.as.csv([undefined])).toBe("null"); // test undefined in array;
Expand Down Expand Up @@ -259,11 +259,11 @@ describe("Method as.array", function () {
it("must correctly reject invalid parameters", function () {

var err = " is not an Array object.";
expect(function(){
expect(function () {
pgp.as.array(123);
}).toThrow("'123'" + err);

expect(function(){
expect(function () {
pgp.as.array('');
}).toThrow("''" + err);

Expand Down Expand Up @@ -469,15 +469,30 @@ describe("Method as.format", function () {
_$_Balance: -123.45
})).toBe("'John O''Connor','" + dateSample.toUTCString() + "',true,-123.45");

// + the same for alternative syntax;
expect(pgp.as.format("$( $Nam$E_),$(d_o_b ),$( _active__),$(_$_Balance)", {
$Nam$E_: "John O'Connor",
d_o_b: dateSample,
_active__: true,
_$_Balance: -123.45
})).toBe("'John O''Connor','" + dateSample.toUTCString() + "',true,-123.45");

// test that even one-symbol, special-named properties work correctly;
expect(pgp.as.format("${$}${_}${a}", {
$: 1,
_: 2,
a: 3
})).toBe("123");

// + the same for alternative syntax;
expect(pgp.as.format("$($)$(_)$(a)", {
$: 1,
_: 2,
a: 3
})).toBe("123");

// Both null and undefined properties are formatted as null;
expect(pgp.as.format("${empty1}, ${empty2}", {
expect(pgp.as.format("${empty1}, $(empty2)", {
empty1: null,
empty2: undefined
})).toBe("null, null");
Expand All @@ -490,7 +505,7 @@ describe("Method as.format", function () {
}).toThrow("Property 'prop2' doesn't exist.");

// testing case sensitivity - Positive;
expect(pgp.as.format("${propVal}${PropVal}${propVAL}${PropVAL}", {
expect(pgp.as.format("${propVal}$(PropVal)${propVAL}${PropVAL}", {
propVal: 1,
PropVal: 2,
propVAL: 3,
Expand All @@ -503,13 +518,33 @@ describe("Method as.format", function () {
prop2: [2, ['three']]
})).toBe("'one', array[2,['three']]");

// mixed syntax test;
expect(pgp.as.format("${prop1}, $(prop2), ${prop3^}, $(prop4^)", {
prop1: 'one',
prop2: 'two',
prop3: 'three',
prop4: 'four'
})).toBe("'one', 'two', three, four");

// testing case sensitivity - Negative;
expect(function () {
pgp.as.format("${PropName}", {
propName: 'hello'
});
}).toThrow("Property 'PropName' doesn't exist.");

// wrong-formatted variables tests:
expect(pgp.as.format("$()", {})).toBe("$()");
expect(pgp.as.format("${test)", {})).toBe("${test)");
expect(pgp.as.format("$(test}", {})).toBe("$(test}");
expect(pgp.as.format("$((test))", {})).toBe("$((test))");
expect(pgp.as.format("${{test}}", {})).toBe("${{test}}");
expect(pgp.as.format("$({test})", {})).toBe("$({test})");

expect(pgp.as.format("$(^test)", {})).toBe("$(^test)");
expect(pgp.as.format("${^test}", {})).toBe("${^test}");
expect(pgp.as.format("$(test^^)", {})).toBe("$(test^^)");
expect(pgp.as.format("${test^^}", {})).toBe("${test^^}");
});

it("must correctly inject raw-text variables", function () {
Expand Down

0 comments on commit 5a337e0

Please sign in to comment.