diff --git a/lib/query.js b/lib/query.js index ef424c02..a18a4036 100644 --- a/lib/query.js +++ b/lib/query.js @@ -78,21 +78,12 @@ function $query(ctx, query, values, qrm, config) { if (!pgFormatting && !$npm.utils.isText(query)) { errMsg = isFunc ? "Invalid function name." : "Invalid query format."; } - if (query instanceof PreparedStatement) { - var ps = query.parse(ctx.db.client.secretKey); - if (ps instanceof PreparedStatementError) { - errMsg = ps; + if (query instanceof QueryParameter) { + var qp = query.parse(); + if (qp instanceof Error) { + errMsg = qp; } else { - query = ps; - } - } else { - if (query instanceof ParameterizedQuery) { - var pq = query.parse(); - if (pq instanceof ParameterizedQueryError) { - errMsg = pq; - } else { - query = pq; - } + query = qp; } } } diff --git a/lib/queryFile.js b/lib/queryFile.js index 24121b59..fcef9314 100644 --- a/lib/queryFile.js +++ b/lib/queryFile.js @@ -71,7 +71,7 @@ var $npm = { * @returns {QueryFile} * * @see QueryFileError - * + * * @example * // File sql.js * @@ -159,47 +159,54 @@ function QueryFile(file, options) { * @method QueryFile.prepare * @summary Prepares the query for execution. * @description - * If the the query hasn't been prepared yet, it will read the file - * and process the contents according to the parameters passed into - * the constructor. + * If the the query hasn't been prepared yet, it will read the file and process the contents according + * to the parameters passed into the constructor. * * This method is meant primarily for internal use by the library. + * + * @returns {Boolean} + * The indication of a successful unchanged result: + * - `true` - successful and unchanged result + * - `false` - either changed or unsuccessful result */ this.prepare = function () { var lastMod; if (opt.debug && ready) { try { lastMod = $npm.fs.statSync(file).mtime.getTime(); - if (lastMod !== modTime) { - ready = false; + if (lastMod === modTime) { + return true; } + ready = false; } catch (e) { sql = undefined; ready = false; error = e; - return; + return false; } } - if (!ready) { - try { - sql = $npm.fs.readFileSync(file, 'utf8'); - modTime = lastMod || $npm.fs.statSync(file).mtime.getTime(); - if (opt.minify && !after) { - sql = $npm.minify(sql, {compress: opt.compress}); - } - if (opt.params !== undefined) { - sql = $npm.format(sql, opt.params, {partial: true}); - } - if (opt.minify && after) { - sql = $npm.minify(sql, {compress: opt.compress}); - } - ready = true; - error = undefined; - } catch (e) { - sql = undefined; - error = new $npm.QueryFileError(e, this); + if (ready) { + return true; + } + try { + sql = $npm.fs.readFileSync(file, 'utf8'); + modTime = lastMod || $npm.fs.statSync(file).mtime.getTime(); + if (opt.minify && !after) { + sql = $npm.minify(sql, {compress: opt.compress}); + } + if (opt.params !== undefined) { + sql = $npm.format(sql, opt.params, {partial: true}); + } + if (opt.minify && after) { + sql = $npm.minify(sql, {compress: opt.compress}); } + ready = true; + error = undefined; + } catch (e) { + sql = undefined; + error = new $npm.QueryFileError(e, this); } + return false; }; /** diff --git a/lib/types/parameterized.js b/lib/types/parameterized.js index 8c9e9349..dc6666af 100644 --- a/lib/types/parameterized.js +++ b/lib/types/parameterized.js @@ -7,8 +7,6 @@ var $npm = { QueryFile: require('../queryFile') }; -var advancedProperties = ['binary', 'rowMode']; - /** * @constructor ParameterizedQuery * @description @@ -78,13 +76,30 @@ function ParameterizedQuery(text, values) { return new ParameterizedQuery(text, values); } + var currentError, PQ = {}, changed = true, state = { + text: text, + values: values, + binary: undefined, + rowMode: undefined + }; + /** * @name ParameterizedQuery#text * @type {string|QueryFile} * @description * A non-empty query string or a {@link QueryFile} object. */ - this.text = text; + Object.defineProperty(this, 'text', { + get: function () { + return state.text; + }, + set: function (value) { + if (value !== state.text) { + state.text = value; + changed = true; + } + } + }); /** * @name ParameterizedQuery#values @@ -92,45 +107,82 @@ function ParameterizedQuery(text, values) { * @description * Query formatting values. It can be either an `Array` or `null`/`undefined`. */ - this.values = values; + Object.defineProperty(this, 'values', { + get: function () { + return state.values; + }, + set: function (value) { + if (value !== state.values) { + state.values = value; + if ($npm.utils.isNull(value) || Array.isArray(value)) { + PQ.values = value; + } else { + changed = true; + } + } + } + }); + + /** + * @name ParameterizedQuery#binary + * @type {Boolean} + * @description + * Activates binary result mode. The default is the text mode. + * + * @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query} + */ + Object.defineProperty(this, 'binary', { + get: function () { + return state.binary; + }, + set: function (value) { + if (value !== state.binary) { + state.binary = value; + changed = true; + } + } + }); + + /** + * @name ParameterizedQuery#rowMode + * @type {String} + * @description + * Changes the way data arrives to the client, with only one value supported by $[pg]: + * - `rowMode = 'array'` will make all data rows arrive as arrays of values. + * By default, rows arrive as objects. + */ + Object.defineProperty(this, 'rowMode', { + get: function () { + return state.rowMode; + }, + set: function (value) { + if (value !== state.rowMode) { + state.rowMode = value; + changed = true; + } + } + }); /** * @name ParameterizedQuery#error * @type {ParameterizedQueryError} + * @readonly * @description * When in an error state, it is set to a {@link ParameterizedQueryError} object. Otherwise, it is `undefined`. * * This property is meant primarily for internal use by the library. */ - this.error = undefined; + Object.defineProperty(this, 'error', { + get: function () { + return currentError; + } + }); if ($npm.utils.isObject(text, ['text'])) { - this.text = text.text; - this.values = text.values; - - /** - * @name ParameterizedQuery#binary - * @type {Boolean} - * @description - * Activates binary result mode. The default is the text mode. - * - * @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query} - */ - - /** - * @name ParameterizedQuery#rowMode - * @type {String} - * @description - * Changes the way data arrives to the client, with only one value supported by $[pg]: - * - `rowMode = 'array'` will make all data rows arrive as arrays of values. - * By default, rows arrive as objects. - */ - - advancedProperties.$forEach(function (prop) { - if (prop in text) { - this[prop] = text[prop]; - } - }, this); + state.text = text.text; + state.values = text.values; + state.binary = text.binary; + state.rowMode = text.rowMode; } /** @@ -144,45 +196,57 @@ function ParameterizedQuery(text, values) { * @returns {{text, values}|ParameterizedQueryError} */ this.parse = function () { - this.error = undefined; - var errors = [], pq = {}; - if (this.text instanceof $npm.QueryFile) { - var qf = this.text; + + var qf = state.text instanceof $npm.QueryFile ? state.text : null; + + if (!changed && !qf) { + return PQ; + } + + changed = true; + PQ = {}; + currentError = undefined; + var errors = []; + + if (qf) { qf.prepare(); if (qf.error) { - pq.text = this.text; + PQ.text = state.text; errors.push(qf.error); } else { - pq.text = qf.query; + PQ.text = qf.query; } } else { - pq.text = this.text; + PQ.text = state.text; } - if (!$npm.utils.isText(pq.text)) { + if (!$npm.utils.isText(PQ.text)) { errors.push("Property 'text' must be a non-empty text string."); } - if (!$npm.utils.isNull(this.values)) { - if (Array.isArray(this.values)) { - if (this.values.length > 0) { - pq.values = this.values; + if (!$npm.utils.isNull(state.values)) { + if (Array.isArray(state.values)) { + if (state.values.length > 0) { + PQ.values = state.values; } } else { errors.push("Property 'values' must be an array or null/undefined."); } } - advancedProperties.$forEach(function (prop) { - if (prop in this) { - pq[prop] = this[prop]; - } - }, this); + if (state.binary !== undefined) { + PQ.binary = state.binary; + } + + if (state.rowMode !== undefined) { + PQ.rowMode = state.rowMode; + } if (errors.length) { - this.error = new $npm.errors.ParameterizedQueryError(errors[0], pq); - return this.error; + return currentError = new $npm.errors.ParameterizedQueryError(errors[0], PQ); } - return pq; + changed = false; + + return PQ; }; } @@ -201,20 +265,23 @@ function ParameterizedQuery(text, values) { ParameterizedQuery.prototype.toString = function (level) { level = level > 0 ? parseInt(level) : 0; var gap = $npm.utils.messageGap(level + 1); - var ps = this.parse(); + var pq = this.parse(); var lines = [ - 'ParameterizedQuery {', - gap + 'text: ' + JSON.stringify(ps.text) + 'ParameterizedQuery {' ]; + if ($npm.utils.isText(pq.text)) { + lines.push(gap + 'text: "' + pq.text + '"'); + } if (this.values !== undefined) { lines.push(gap + 'values: ' + JSON.stringify(this.values)); } - advancedProperties.$forEach(function (prop) { - if (prop in this) { - lines.push(gap + prop + ': ' + JSON.stringify(this[prop])); - } - }, this); - if (this.error) { + if (this.binary !== undefined) { + lines.push(gap + 'binary: ' + JSON.stringify(this.binary)); + } + if (this.rowMode !== undefined) { + lines.push(gap + 'rowMode: ' + JSON.stringify(this.rowMode)); + } + if (this.error !== undefined) { lines.push(gap + 'error: ' + this.error.toString(level + 1)); } lines.push($npm.utils.messageGap(level) + '}'); diff --git a/lib/types/prepared.js b/lib/types/prepared.js index 5d7306bd..77fd545c 100644 --- a/lib/types/prepared.js +++ b/lib/types/prepared.js @@ -7,8 +7,6 @@ var $npm = { QueryFile: require('../queryFile') }; -var advancedProperties = ['binary', 'rowMode', 'rows']; - /** * @constructor PreparedStatement * @description @@ -18,7 +16,7 @@ var advancedProperties = ['binary', 'rowMode', 'rows']; * * The alternative syntax supports advanced properties {@link PreparedStatement#binary binary}, {@link PreparedStatement#rowMode rowMode} * and {@link PreparedStatement#rows rows}, which are passed into $[pg], but not used by the class. - * + * * All properties can also be set after the object's construction. * * This type extends the basic `{name, text, values}` object, by replacing it, i.e. when the basic object is used @@ -83,9 +81,13 @@ function PreparedStatement(name, text, values) { return new PreparedStatement(name, text, values); } - var lastSessionId = -1, state = { + var currentError, PS = {}, changed = true, state = { name: name, - text: text + text: text, + values: values, + binary: undefined, + rowMode: undefined, + rows: undefined }; /** @@ -100,9 +102,9 @@ function PreparedStatement(name, text, values) { return state.name; }, set: function (value) { - if (state.name !== value) { + if (value !== state.name) { state.name = value; - lastSessionId = -1; + changed = true; } } }); @@ -112,15 +114,18 @@ function PreparedStatement(name, text, values) { * @type {string|QueryFile} * @description * A non-empty query string or a {@link QueryFile} object. + * + * Changing this property for the same {@link PreparedStatement#name name} will have no effect, because queries + * for Prepared Statements are cached, with {@link PreparedStatement#name name} being the cache key. */ Object.defineProperty(this, 'text', { get: function () { return state.text; }, set: function (value) { - if (state.text !== value) { + if (value !== state.text) { state.text = value; - lastSessionId = -1; + changed = true; } } }); @@ -131,7 +136,80 @@ function PreparedStatement(name, text, values) { * @description * Query formatting values. It can be either an `Array` or `null`/`undefined`. */ - this.values = values; + Object.defineProperty(this, 'values', { + get: function () { + return state.values; + }, + set: function (value) { + if (value !== state.values) { + state.values = value; + if ($npm.utils.isNull(value) || Array.isArray(value)) { + PS.values = value; + } else { + changed = true; + } + } + } + }); + + /** + * @name PreparedStatement#binary + * @type {Boolean} + * @description + * Activates binary result mode. The default is the text mode. + * + * @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query} + */ + Object.defineProperty(this, 'binary', { + get: function () { + return state.binary; + }, + set: function (value) { + if (value !== state.binary) { + state.binary = value; + changed = true; + } + } + }); + + /** + * @name PreparedStatement#rowMode + * @type {String} + * @description + * Changes the way data arrives to the client, with only one value supported by $[pg]: + * - `rowMode = 'array'` will make all data rows arrive as arrays of values. + * By default, rows arrive as objects. + */ + Object.defineProperty(this, 'rowMode', { + get: function () { + return state.rowMode; + }, + set: function (value) { + if (value !== state.rowMode) { + state.rowMode = value; + changed = true; + } + } + }); + + /** + * @name PreparedStatement#rows + * @type {Number} + * @description + * Number of rows to return at a time from a Prepared Statement's portal. + * The default is 0, which means that all rows must be returned at once. + */ + Object.defineProperty(this, 'rows', { + get: function () { + return state.rows; + }, + set: function (value) { + if (value !== state.rows) { + state.rows = value; + changed = true; + } + } + }); /** * @name PreparedStatement#error @@ -141,44 +219,19 @@ function PreparedStatement(name, text, values) { * * This property is meant primarily for internal use by the library. */ - this.error = undefined; + Object.defineProperty(this, 'error', { + get: function () { + return currentError; + } + }); if ($npm.utils.isObject(name, ['name'])) { - this.name = name.name; - this.text = name.text; - this.values = name.values; - - /** - * @name PreparedStatement#binary - * @type {Boolean} - * @description - * Activates binary result mode. The default is the text mode. - * - * @see {@link http://www.postgresql.org/docs/devel/static/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY Extended Query} - */ - - /** - * @name PreparedStatement#rowMode - * @type {String} - * @description - * Changes the way data arrives to the client, with only one value supported by $[pg]: - * - `rowMode = 'array'` will make all data rows arrive as arrays of values. - * By default, rows arrive as objects. - */ - - /** - * @name PreparedStatement#rows - * @type {Number} - * @description - * Number of rows to return at a time from a Prepared Statement's portal. - * The default is 0, which means that all rows must be returned at once. - */ - - advancedProperties.$forEach(function (prop) { - if (prop in name) { - this[prop] = name[prop]; - } - }, this); + state.name = name.name; + state.text = name.text; + state.values = name.values; + state.binary = name.binary; + state.rowMode = name.rowMode; + state.rows = name.rows; } /** @@ -189,69 +242,70 @@ function PreparedStatement(name, text, values) { * * This method is meant primarily for internal use by the library. * - * @param {number} [sessionId] - * Current connection id. Based on the value, the method determines when property `text` should - * be included or skipped within the returned object. - * * @returns {{name, text, values}|PreparedStatementError} */ - this.parse = function (sessionId) { - this.error = undefined; - var errors = [], ps = { - name: this.name + this.parse = function () { + + var qf = state.text instanceof $npm.QueryFile ? state.text : null; + + if (!changed && !qf) { + return PS; + } + + changed = true; + PS = { + name: state.name }; - if (!$npm.utils.isText(this.name)) { + currentError = undefined; + var errors = []; + + if (!$npm.utils.isText(PS.name)) { errors.push("Property 'name' must be a non-empty text string."); } - if (this.text instanceof $npm.QueryFile) { - var qf = this.text, prevQuery = qf.query; + + if (qf) { qf.prepare(); if (qf.error) { - ps.text = this.text; + PS.text = state.text; errors.push(qf.error); } else { - if (sessionId === lastSessionId) { - if (qf.query !== prevQuery) { - ps.text = qf.query; - } - } else { - lastSessionId = sessionId || lastSessionId; - if (sessionId || lastSessionId < 0) { - ps.text = qf.query; - } - } + PS.text = qf.query; } } else { - if ((sessionId || lastSessionId < 0) && sessionId !== lastSessionId) { - lastSessionId = sessionId || lastSessionId; - ps.text = this.text; - } + PS.text = state.text; } - if ('text' in ps && !$npm.utils.isText(ps.text)) { + if (!$npm.utils.isText(PS.text)) { errors.push("Property 'text' must be a non-empty text string."); } - if (!$npm.utils.isNull(this.values)) { - if (Array.isArray(this.values)) { - if (this.values.length > 0) { - ps.values = this.values; + if (!$npm.utils.isNull(state.values)) { + if (Array.isArray(state.values)) { + if (state.values.length > 0) { + PS.values = state.values; } } else { errors.push("Property 'values' must be an array or null/undefined."); } } - advancedProperties.$forEach(function (prop) { - if (prop in this) { - ps[prop] = this[prop]; - } - }, this); + if (state.binary !== undefined) { + PS.binary = state.binary; + } + + if (state.rowMode !== undefined) { + PS.rowMode = state.rowMode; + } + + if (state.rows !== undefined) { + PS.rows = state.rows; + } if (errors.length) { - this.error = new $npm.errors.PreparedStatementError(errors[0], ps); - return this.error; + return currentError = new $npm.errors.PreparedStatementError(errors[0], PS); } - return ps; + changed = false; + + return PS; }; } @@ -270,24 +324,26 @@ function PreparedStatement(name, text, values) { PreparedStatement.prototype.toString = function (level) { level = level > 0 ? parseInt(level) : 0; var gap = $npm.utils.messageGap(level + 1); + var ps = this.parse(); var lines = [ - 'PreparedStatement {' + 'PreparedStatement {', + gap + 'name: ' + JSON.stringify(this.name) ]; - var ps = this.parse(); - if ($npm.utils.isText(this.name)) { - lines.push(gap + 'name: "' + this.name + '"'); - } if ($npm.utils.isText(ps.text)) { lines.push(gap + 'text: "' + ps.text + '"'); } if (this.values !== undefined) { lines.push(gap + 'values: ' + JSON.stringify(this.values)); } - advancedProperties.$forEach(function (prop) { - if (prop in this) { - lines.push(gap + prop + ': ' + JSON.stringify(this[prop])); - } - }, this); + if (this.binary !== undefined) { + lines.push(gap + 'binary: ' + JSON.stringify(this.binary)); + } + if (this.rowMode !== undefined) { + lines.push(gap + 'rowMode: ' + JSON.stringify(this.rowMode)); + } + if (this.rows !== undefined) { + lines.push(gap + 'rows: ' + JSON.stringify(this.rows)); + } if (this.error) { lines.push(gap + 'error: ' + this.error.toString(level + 1)); } diff --git a/package.json b/package.json index 5d84f17f..60bbb3dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pg-promise", - "version": "4.2.6", + "version": "4.2.7", "description": "PostgreSQL via promises", "main": "lib/index.js", "scripts": { diff --git a/test/fileSpec.js b/test/fileSpec.js index b03be372..28e2d4ad 100644 --- a/test/fileSpec.js +++ b/test/fileSpec.js @@ -158,6 +158,18 @@ describe("QueryFile / Positive:", function () { expect(qf.error).toBeUndefined(); }); }); + + describe("repeated read", function () { + // this is just for code coverage; + it("must not read again", function () { + var qf = new QueryFile(sqlSimple, {debug: false, minify: true}); + var res1 = qf.prepare(); + var res2 = qf.prepare(); + expect(qf.query).toBe('select 1;'); + expect(res1).toBe(true); + expect(res2).toBe(true); + }); + }); }); describe("QueryFile / Negative:", function () { diff --git a/test/parameterizedSpec.js b/test/parameterizedSpec.js index 61a1fa80..dc408934 100644 --- a/test/parameterizedSpec.js +++ b/test/parameterizedSpec.js @@ -11,6 +11,8 @@ var dbHeader = header(options); var pgp = dbHeader.pgp; var db = dbHeader.db; +var ParameterizedQueryError = pgp.errors.ParameterizedQueryError; + function dummy() { // dummy/empty function; } @@ -31,21 +33,58 @@ describe("ParameterizedQuery", function () { }); }); - describe("advanced properties", function () { - var input = { - text: 'text-text', - values: [123], - binary: true, - rowMode: 'array' - }; - var pq = new pgp.ParameterizedQuery(input); - it("must return the complete set of properties", function () { - expect(pq.parse()).toEqual(input); + describe("property values", function () { + var values = [1, 2, 3]; + it("must get correctly", function () { + var pq = new pgp.ParameterizedQuery({ + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array' + }); + expect(pq.text).toBe('original-sql'); + expect(pq.values).toBe(values); + expect(pq.binary).toBe(true); + expect(pq.rowMode).toBe('array'); + expect(pq.inspect()).toBe(pq.toString()); + }); + it("must keep original object when set to the same value", function () { + var pq = new pgp.ParameterizedQuery({ + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array' + }); + var obj1 = pq.parse(); + pq.text = 'original-sql'; + pq.values = values; + pq.binary = true; + pq.rowMode = 'array'; + var obj2 = pq.parse(); + expect(obj1 === obj2).toBe(true); + expect(pq.inspect()).toBe(pq.toString()); + }); + it("must create a new object when changed", function () { + var pq = new pgp.ParameterizedQuery({ + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array' + }); + var obj1 = pq.parse(); + pq.text = 'new text'; + var obj2 = pq.parse(); + pq.values = [1, 2, 3]; + var obj3 = pq.parse(); + pq.binary = false; + var obj4 = pq.parse(); + pq.rowMode = 'new'; + var obj5 = pq.parse(); + expect(obj1 !== obj2 !== obj3 !== obj4 != obj5).toBe(true); expect(pq.inspect()).toBe(pq.toString()); - expect(pq.inspect() != pq.toString(1)).toBe(true); }); }); - + describe("parameters", function () { var pq = new pgp.ParameterizedQuery({text: 'test-query', values: [123], binary: true, rowMode: 'array'}); it("must expose the values correctly", function () { @@ -79,8 +118,8 @@ describe("ParameterizedQuery", function () { .then(function (data) { result = data; }).catch(function (error) { - console.log("ERROR:", error); - }) + console.log("ERROR:", error); + }) .finally(function () { done(); }); @@ -115,12 +154,26 @@ describe("ParameterizedQuery", function () { var qf = new pgp.QueryFile('./invalid.sql'); var pq = new pgp.ParameterizedQuery(qf); var result = pq.parse(); - expect(result instanceof pgp.errors.ParameterizedQueryError).toBe(true); + expect(result instanceof ParameterizedQueryError).toBe(true); expect(result.error instanceof pgp.errors.QueryFileError).toBe(true); expect(pq.toString()).toBe(pq.inspect()); + expect(pq.toString(1) !== pq.inspect()).toBe(true); }); - + }); + + describe("Negative", function () { + describe("invalid 'values'", function () { + var pq = new pgp.ParameterizedQuery('some sql'), + errMsg = "Property 'values' must be an array or null/undefined."; + pq.values = 'one'; + var res = pq.parse(); + it("must return an error", function () { + expect(res instanceof ParameterizedQueryError).toBe(true); + expect(res.message).toBe(errMsg); + }); + }); + }); }); describe("Direct Parameterized Query", function () { @@ -129,8 +182,8 @@ describe("Direct Parameterized Query", function () { var result; beforeEach(function (done) { db.many({ - text: "select * from users" - }) + text: "select * from users" + }) .then(function (data) { result = data; }) @@ -148,9 +201,9 @@ describe("Direct Parameterized Query", function () { var result; beforeEach(function (done) { db.one({ - text: "select * from users where id=$1", - values: [1] - }) + text: "select * from users where id=$1", + values: [1] + }) .then(function (data) { result = data; }) @@ -167,8 +220,8 @@ describe("Direct Parameterized Query", function () { var result; beforeEach(function (done) { db.many({ - text: "select * from somewhere" - }) + text: "select * from somewhere" + }) .then(dummy, function (reason) { result = reason; }) @@ -186,9 +239,9 @@ describe("Direct Parameterized Query", function () { var result; beforeEach(function (done) { db.many({ - text: "select 1", - values: 123 - }) + text: "select 1", + values: 123 + }) .then(dummy, function (reason) { result = reason; }) @@ -206,8 +259,8 @@ describe("Direct Parameterized Query", function () { var result; beforeEach(function (done) { db.query({ - text: null - }) + text: null + }) .then(dummy, function (reason) { result = reason; }) diff --git a/test/preparedSpec.js b/test/preparedSpec.js index 9aa08e8d..f6a2b29e 100644 --- a/test/preparedSpec.js +++ b/test/preparedSpec.js @@ -33,18 +33,68 @@ describe("PreparedStatement", function () { }); }); - describe("advanced properties", function () { - var input = { - name: 'test-name', - text: 'text-text', - values: [123], - binary: true, - rowMode: 'array', - rows: 10 - }; - var ps = new pgp.PreparedStatement(input); - it("must return the complete set of properties", function () { - expect(ps.parse()).toEqual(input); + describe("property values", function () { + var values = [1, 2, 3]; + it("must get correctly", function () { + var ps = new pgp.PreparedStatement({ + name: 'original-name', + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array', + rows: 1 + }); + expect(ps.name).toBe('original-name'); + expect(ps.text).toBe('original-sql'); + expect(ps.values).toBe(values); + expect(ps.binary).toBe(true); + expect(ps.rowMode).toBe('array'); + expect(ps.rows).toBe(1); + expect(ps.inspect()).toBe(ps.toString()); + }); + it("must keep original object when set to the same value", function () { + var ps = new pgp.PreparedStatement({ + name: 'original-name', + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array', + rows: 1 + }); + var obj1 = ps.parse(); + ps.name = 'original-name'; + ps.text = 'original-sql'; + ps.values = values; + ps.binary = true; + ps.rowMode = 'array'; + ps.rows = 1; + var obj2 = ps.parse(); + expect(obj1 === obj2).toBe(true); + expect(ps.inspect()).toBe(ps.toString()); + }); + it("must create a new object when changed", function () { + var ps = new pgp.PreparedStatement({ + name: 'original-name', + text: 'original-sql', + values: values, + binary: true, + rowMode: 'array', + rows: 1 + }); + var obj1 = ps.parse(); + ps.name = 'new value'; + var obj2 = ps.parse(); + ps.text = 'new text'; + var obj3 = ps.parse(); + ps.values = [1, 2, 3]; + var obj4 = ps.parse(); + ps.binary = false; + var obj5 = ps.parse(); + ps.rowMode = 'new'; + var obj6 = ps.parse(); + ps.rows = 3; + var obj7 = ps.parse(); + expect(obj1 !== obj2 !== obj3 !== obj4 != obj5 != obj6 != obj7).toBe(true); expect(ps.inspect()).toBe(ps.toString()); }); }); @@ -92,8 +142,8 @@ describe("PreparedStatement", function () { .then(function (data) { result = data; }).catch(function (error) { - console.log("ERROR:", error); - }) + console.log("ERROR:", error); + }) .finally(function () { done(); }); @@ -133,37 +183,18 @@ describe("PreparedStatement", function () { expect(result.error instanceof pgp.errors.QueryFileError).toBe(true); expect(ps.toString()).toBe(ps.inspect()); }); + }); - describe("changing query on the fly", function () { - var file = './test/sql/simple.sql'; - var qf = new pgp.QueryFile(file, {debug: true}); - var ps = new pgp.PreparedStatement('test-name', qf, []); - var result1 = ps.parse(123), result2, result3; - var query = qf.query; - - ps.parse(); - - beforeEach(function (done) { - fs.writeFile(file, 'temporary sql', 'utf8', function () { - var modTime1 = fs.statSync(file).mtime.getTime(); - result2 = ps.parse(123); - fs.writeFile(file, query, 'utf8', function () { - var t = new Date(); - t.setTime(t.getTime() + 60 * 60 * 1000); - fs.utimesSync(file, t, t); - var modTime2 = fs.statSync(file).mtime.getTime(); - result3 = ps.parse(123); - ps.parse(123); - done(); - }); - }); - }); - - it("must be picked up", function () { - expect(result1).toEqual(result3); + describe("Negative", function () { + describe("invalid 'values'", function () { + var ps = new pgp.PreparedStatement('test-name', 'some sql'); + ps.values = 'one'; + var res = ps.parse(); + it("must return an error", function () { + expect(res instanceof PreparedStatementError).toBe(true); + expect(res.message).toBe("Property 'values' must be an array or null/undefined."); }); }); - }); }); @@ -173,9 +204,10 @@ describe("Direct Prepared Statements", function () { var result; beforeEach(function (done) { db.many({ - name: "get all users", - text: "select * from users" - }) + name: "get all users", + text: "select * from users", + values: [] + }) .then(function (data) { result = data; }) @@ -193,10 +225,10 @@ describe("Direct Prepared Statements", function () { var result; beforeEach(function (done) { db.one({ - name: "find one user", - text: "select * from users where id=$1", - values: [1] - }) + name: "find one user", + text: "select * from users where id=$1", + values: [1] + }) .then(function (data) { result = data; }) @@ -213,9 +245,9 @@ describe("Direct Prepared Statements", function () { var result; beforeEach(function (done) { db.many({ - name: "break it", - text: "select * from somewhere" - }) + name: "break it", + text: "select * from somewhere" + }) .then(dummy, function (reason) { result = reason; }) @@ -233,10 +265,10 @@ describe("Direct Prepared Statements", function () { var result; beforeEach(function (done) { db.many({ - name: "invalid", - text: "select 1", - values: 123 - }) + name: "invalid", + text: "select 1", + values: 123 + }) .then(dummy, function (reason) { result = reason; }) @@ -272,9 +304,9 @@ describe("Direct Prepared Statements", function () { var result; beforeEach(function (done) { db.query({ - name: "non-empty", - text: null - }) + name: "non-empty", + text: null + }) .then(dummy, function (reason) { result = reason; })