diff --git a/README.md b/README.md index 27df2390..d5d02a03 100644 --- a/README.md +++ b/README.md @@ -379,8 +379,11 @@ db.proc(query, values); // calls db.func(query, values, queryResult.one | queryR ``` ### Conversion Helpers + The library provides several helper functions to convert basic javascript types into their proper PostgreSQL presentation that can be passed -directly into queries or functions as parameters. All of such helper functions are located within namespace ```pgp.as```: +directly into queries or functions as parameters. All of such helper functions are located within namespace `pgp.as`, and each function +returns a formatted string when successful or throws an error when it fails. + ```javascript pgp.as.bool(value); // returns proper PostgreSQL boolean presentation; @@ -393,18 +396,9 @@ pgp.as.date(value); // returns proper PostgreSQL date/time presentation, pgp.as.csv(array); // returns a CSV string with values formatted according // to their type, using the above methods; -pgp.as.format(query, values, se); +pgp.as.format(query, values); // Replaces variables in a query with their `values` as specified. // `values` is either a simple value or an array of simple values. - // Returns formatted query string, if successful, or throws an error - // when it fails. - // `se` - Suppress Errors. It is an optional parameter, which when set, - // forces return of an object: - // { - // success: true/false, - // query: resulting query text, if success=true, otherwise it is undefined; - // error: error description, if success=false, otherwise it is undefined; - // } ``` As these helpers are not associated with any database, they can be used from anywhere. diff --git a/index.js b/index.js index 1ff0e8bf..2095d539 100644 --- a/index.js +++ b/index.js @@ -282,79 +282,43 @@ function $createFuncQuery(funcName, values) { // 'pg-promise' own query formatting solution; // It parses query for $1, $2,... variables and replaces them with the values passed; // 'values' can be an array of simple values or just one simple value; -// When fails, the function throws an error, unless 'se' was set. -// 'se' - suppress errors (optional). When set, the function will never throw an error; -// Instead, it will always return an object: -// { -// success: true/false, -// query: resulting query text, if success=true, otherwise it is undefined; -// error: error description, if success=false, otherwise it is undefined; -// } -function $formatQuery(query, values, se) { - var result = { - success: true - }; +// When fails, the function throws an error. +function $formatQuery(query, values) { if (typeof(query) !== 'string') { - result.success = false; - result.error = "Parameter 'query' must be a text string."; - } else { - if (Array.isArray(values)) { - for (var i = 0; i < values.length; i++) { - // variable name must exist and not be followed by a digit; - var pattern = '\\$' + (i + 1) + '(?!\\d)'; - if (query.search(pattern) === -1) { - result.success = false; - result.error = "More values passed in array than variables in the query."; - break; - } else { - // expect a simple value; - var value = $wrapValue(values[i]); - if (value === null) { - // error: not a simple value; - result.success = false; - result.error = "Cannot convert parameter with index " + i; - break; - } else { - var reg = new RegExp(pattern, 'g'); - query = query.replace(reg, value); - } - } + throw new Error("Parameter 'query' must be a text string."); + } + if (Array.isArray(values)) { + for (var i = 0; i < values.length; i++) { + // variable name must exist and not be followed by a digit; + var pattern = '\\$' + (i + 1) + '(?!\\d)'; + if (query.search(pattern) === -1) { + throw new Error("More values passed in array than variables in the query."); } - } else { - if (values !== undefined) { - // variable name must exist and not be followed by a digit; - if (query.search(/\$1(?!\d)/) === -1) { - // a single value was passed, but variable $1 doesn't exist; - result.success = false; - result.error = "No variable found in the query to replace with the passed value."; - } else { - // expect a simple value; - var value = $wrapValue(values); - if (value === null) { - // error: not a simple value; - result.success = false; - result.error = "Cannot convert type '" + typeof(values) + "' into a query variable value."; - } else { - query = query.replace(/\$1(?!\d)/g, value); - } - } + // expect a simple value; + var value = $wrapValue(values[i]); + if (value === null) { + // error: not a simple value; + throw new Error("Cannot convert parameter with index " + i); } + query = query.replace(new RegExp(pattern, 'g'), value); } - } - if (result.success) { - result.query = query; - } - if (se) { - // suppress errors; - return result; } else { - // errors are not to be suppressed; - if (result.success) { - return result.query; - } else { - throw new Error(result.error); + if (values !== undefined) { + // variable name must exist and not be followed by a digit; + if (query.search(/\$1(?!\d)/) === -1) { + throw new Error("No variable found in the query to replace with the passed value."); + } else { + // expect a simple value; + var value = $wrapValue(values); + if (value === null) { + // error: not a simple value; + throw new Error("Cannot convert type '" + typeof(values) + "' into a query variable value."); + } + query = query.replace(/\$1(?!\d)/g, value); + } } } + return query; } // Generic, static query call; @@ -363,7 +327,7 @@ function $query(client, query, values, qrm, options) { if ($isDBNull(qrm)) { qrm = queryResult.any; // default query result; } - var errMsg, req, pgFormatting = (options && options.pgFormatting); + var errMsg, pgFormatting = (options && options.pgFormatting); if (!query) { errMsg = "Invalid query specified."; } else { @@ -371,17 +335,12 @@ function $query(client, query, values, qrm, options) { if ((qrm & badMask) === badMask || qrm < 1 || qrm > 6) { errMsg = "Invalid Query Result Mask specified."; } else { - if (pgFormatting) { - // 'node-postgres' will do the parameter formatting for us; - req = { - success: true, - query: query - }; - } else { + if (!pgFormatting) { // use 'pg-promise' implementation of parameter formatting; - req = $formatQuery(query, values, true); - if (!req.success) { - errMsg = req.error; + try { + query = $formatQuery(query, values); + } catch (err) { + errMsg = err.message; } } } @@ -396,14 +355,14 @@ function $query(client, query, values, qrm, options) { throw new Error("Function was expected for 'options.query'"); } try { - func(client, req.query, params); // notify the client; + func(client, query, params); // notify the client; } catch (err) { reject(err); return; } } try { - client.query(req.query, params, function (err, result) { + client.query(query, params, function (err, result) { if (err) { reject(err.message); } else { @@ -411,10 +370,10 @@ function $query(client, query, values, qrm, options) { var l = result.rows.length; if (l) { if (l > 1 && (qrm & queryResult.one)) { - reject("Single row was expected from query: " + req.query); + reject("Single row was expected from query: " + query); } else { if (!(qrm & (queryResult.one | queryResult.many))) { - reject("No return data was expected from query: " + req.query); + reject("No return data was expected from query: " + query); } else { if (!(qrm & queryResult.many)) { data = result.rows[0]; @@ -425,7 +384,7 @@ function $query(client, query, values, qrm, options) { if (qrm & queryResult.none) { data = (qrm & queryResult.many) ? [] : null; } else { - reject("No rows returned from query: " + req.query); + reject("No rows returned from query: " + query); } } resolve(data); diff --git a/package.json b/package.json index 8eb54e3b..15921daf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pg-promise", - "version": "0.7.0", + "version": "0.7.1", "description": "PG + Promises/A+, with transactions.", "main": "index.js", "scripts": { diff --git a/test/indexSpec.js b/test/indexSpec.js index ee915ca8..75746244 100644 --- a/test/indexSpec.js +++ b/test/indexSpec.js @@ -219,12 +219,7 @@ describe("Method as.csv", function () { describe("Method as.format", function () { - // There is absolutely no point in repeating all tests when - // parameter 'se' is not set, so we do just basic testing here - // to see that exception is being thrown when 'se' is not set. - // And most of tests are done with 'se' set in the section that - // follows after. - it("must throw an error when it fails, if 'se' is not set", function () { + it("must return a correctly formatted string or throw an error", function () { expect(function () { pgp.as.format(); @@ -234,6 +229,10 @@ describe("Method as.format", function () { pgp.as.format(null); }).toThrow("Parameter 'query' must be a text string."); + expect(function () { + pgp.as.format(null, [1, 2, 3]); + }).toThrow("Parameter 'query' must be a text string."); + expect(function () { pgp.as.format(123); }).toThrow("Parameter 'query' must be a text string."); @@ -242,122 +241,61 @@ describe("Method as.format", function () { pgp.as.format("", [123]); }).toThrow("More values passed in array than variables in the query."); + expect(function () { + pgp.as.format("", 123); + }).toThrow("No variable found in the query to replace with the passed value."); + + expect(function () { + pgp.as.format("", null); + }).toThrow("No variable found in the query to replace with the passed value."); + expect(function () { pgp.as.format("$1", [{}]); }).toThrow("Cannot convert parameter with index 0"); + expect(function () { + pgp.as.format("$1", {}); + }).toThrow("Cannot convert type 'object' into a query variable value."); + expect(function () { pgp.as.format("$1, $2", ['one', {}]); }).toThrow("Cannot convert parameter with index 1"); - }); - - // Almost all tests are here, with `se` set, because it doesn't make - // sense repeating all the tests, given the implementation logic of the method: - // it just switches the output result from an object to throwing an error. - - it("must return a correctly formatted object, if 'se' is set", function () { - - expect(typeof(pgp.as.format(undefined, undefined, true))).toBe("object"); - expect(typeof(pgp.as.format(null, undefined, true))).toBe("object"); - expect(typeof(pgp.as.format("", undefined, true))).toBe("object"); - expect(typeof(pgp.as.format("", [], true))).toBe("object"); - - var q = pgp.as.format(undefined, undefined, true); - expect(q.success).toBe(false); - expect(q.error).toBe("Parameter 'query' must be a text string."); - - q = pgp.as.format(null, undefined, true); - expect(q.success).toBe(false); - expect(q.error).toBe("Parameter 'query' must be a text string."); + expect(pgp.as.format("", [])).toBe(""); + expect(pgp.as.format("$1", [])).toBe("$1"); + expect(pgp.as.format("$1")).toBe("$1"); + expect(pgp.as.format("$1", null)).toBe("null"); - q = pgp.as.format("", undefined, true); - expect(q.success).toBe(true); - expect(q.query).toBe(""); + expect(pgp.as.format("$1", [undefined])).toBe("null"); - q = pgp.as.format(null, [1, 2, 3], true); - expect(q.success).toBe(false); - expect(q.error).toBe("Parameter 'query' must be a text string."); + expect(pgp.as.format("$1", "one")).toBe("'one'"); + expect(pgp.as.format("$1", ["one"])).toBe("'one'"); - q = pgp.as.format("$1", null, true); - expect(q.success).toBe(true); - expect(q.query).toBe("null"); + expect(pgp.as.format("$1, $1", "one")).toBe("'one', 'one'"); - q = pgp.as.format("", null, true); - expect(q.success).toBe(false); - expect(q.error).toBe("No variable found in the query to replace with the passed value."); + expect(pgp.as.format("$1$1", "one")).toBe("'one''one'"); - expect(pgp.as.format("$1", undefined, true).success).toBe(true); - expect(pgp.as.format("$1", undefined, true).query).toBe("$1"); + expect(pgp.as.format("$1, $2, $3, $4", [true, -12.34, "text", dateSample])).toBe("TRUE, -12.34, 'text', '" + dateSample.toUTCString() + "'"); - expect(pgp.as.format("$1", [], true).success).toBe(true); - expect(pgp.as.format("$1", [], true).query).toBe("$1"); + expect(pgp.as.format("$1 $1, $2 $2, $1", [1, "two"])).toBe("1 1, 'two' 'two', 1"); // test for repeated variables; - q = pgp.as.format("$1", [undefined], true); - expect(q.success).toBe(true); - expect(q.query).toBe("null"); + expect(pgp.as.format("Test: $1", ["don't break quotes!"])).toBe("Test: 'don''t break quotes!'"); - q = pgp.as.format("$1", ["one"], true); - expect(q.success).toBe(true); - expect(q.query).toBe("'one'"); - - q = pgp.as.format("$1", "one", true); - expect(q.success).toBe(true); - expect(q.query).toBe("'one'"); - - q = pgp.as.format("$1, $1", "one", true); - expect(q.success).toBe(true); - expect(q.query).toBe("'one', 'one'"); - - q = pgp.as.format("$1, $2, $3, $4", [true, -12.34, "text", dateSample], true); - expect(q.success).toBe(true); - expect(q.query).toBe("TRUE, -12.34, 'text', '" + dateSample.toUTCString() + "'"); - - q = pgp.as.format("$1 $1, $2 $2, $1", [1, "two"], true); // test for repeated variables; - expect(q.success).toBe(true); - expect(q.query).toBe("1 1, 'two' 'two', 1"); - - q = pgp.as.format("Test: $1", ["don't break quotes!"], true); - expect(q.success).toBe(true); - expect(q.query).toBe("Test: 'don''t break quotes!'"); - - q = pgp.as.format("", [1], true); - expect(q.success).toBe(false); - expect(q.error).toBe("More values passed in array than variables in the query."); - - q = pgp.as.format("", 1, true); - expect(q.success).toBe(false); - expect(q.error).toBe("No variable found in the query to replace with the passed value."); - - q = pgp.as.format("$1", {}, true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert type 'object' into a query variable value."); - - q = pgp.as.format("$1", function () { - }, true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert type 'function' into a query variable value."); - - q = pgp.as.format("$1", [{}], true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert parameter with index 0"); - - q = pgp.as.format("$1, $2", [true, function () { - }], true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert parameter with index 1"); + expect(function(){ + pgp.as.format("$1,$2", [{}, {}]); + }).toThrow("Cannot convert parameter with index 0"); // test that errors in type conversion are // detected and reported from left to right; - q = pgp.as.format("$1,$2", [{}, {}], true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert parameter with index 0"); + expect(function(){ + pgp.as.format("$1, $2", [true, function () {}]); + }).toThrow("Cannot convert parameter with index 1"); // test that once a conversion issue is encountered, // the rest of parameters are not verified; - q = pgp.as.format("$1,$2", [1, {}, 2, 3, 4, 5], true); - expect(q.success).toBe(false); - expect(q.error).toBe("Cannot convert parameter with index 1"); + expect(function(){ + pgp.as.format("$1,$2", [1, {}, 2, 3, 4, 5]); + }).toThrow("Cannot convert parameter with index 1"); // testing with lots of variables; var source = "", dest = "", params = []; @@ -366,39 +304,32 @@ describe("Method as.format", function () { dest += i; params.push(i); } - q = pgp.as.format(source, params, true); - expect(q.success).toBe(true); - expect(q.query).toBe(dest); + expect(pgp.as.format(source, params)).toBe(dest); // testing various cases with many variables: // - variables next to each other; // - variables not defined; // - variables are repeated; // - long variable names present; - q = pgp.as.format("$1$2,$3,$4,$5,$6,$7,$8,$9,$10$11,$12,$13,$14,$15,$1,$3", [1, 2, 'C', 'DDD', 'E', 'F', 'G', 'H', 'I', 88, 99, 'LLL'], true); - expect(q.success).toBe(true); - expect(q.query).toBe("12,'C','DDD','E','F','G','H','I',8899,'LLL',$13,$14,$15,1,'C'"); + expect(pgp.as.format("$1$2,$3,$4,$5,$6,$7,$8,$9,$10$11,$12,$13,$14,$15,$1,$3", [1, 2, 'C', 'DDD', 'E', 'F', 'G', 'H', 'I', 88, 99, 'LLL'])) + .toBe("12,'C','DDD','E','F','G','H','I',8899,'LLL',$13,$14,$15,1,'C'"); // test that $1 variable isn't confused with $12; - q = pgp.as.format("$12", 123, true); - expect(q.success).toBe(false); - expect(q.error).toBe("No variable found in the query to replace with the passed value."); + expect(function(){ + pgp.as.format("$12", 123); + }).toThrow("No variable found in the query to replace with the passed value."); // test that $1 variable isn't confused with $112 - q = pgp.as.format("$112", 123, true); - expect(q.success).toBe(false); - expect(q.error).toBe("No variable found in the query to replace with the passed value."); + expect(function(){ + pgp.as.format("$112", 123); + }).toThrow("No variable found in the query to replace with the passed value."); // test that variable names are not confused for longer ones; - q = pgp.as.format("$11, $1, $111, $1", 123, true); - expect(q.success).toBe(true); - expect(q.query).toBe("$11, 123, $111, 123"); + expect(pgp.as.format("$11, $1, $111, $1", 123)).toBe("$11, 123, $111, 123"); // test that variable names are not confused for longer ones, // even when they are right next to each other; - q = pgp.as.format("$11$1$111$1", 123, true); - expect(q.success).toBe(true); - expect(q.query).toBe("$11123$111123"); + expect(pgp.as.format("$11$1$111$1", 123)).toBe("$11123$111123"); }); });