diff --git a/lib/statement/order-by.js b/lib/statement/order-by.js index c7ccc69656c618516eeb171a77434d0c3b53166b..f1c2cc2047538e4cbdbb3fe5225f33e697ca0a18 100644 --- a/lib/statement/order-by.js +++ b/lib/statement/order-by.js @@ -30,13 +30,15 @@ exports = module.exports = (order, useBody) => { exports.fullAttribute = function (orderObj, useBody) { let field; + const jsonAsText = !!orderObj.type; // Explicit casts must use as-text operators if (orderObj.expr) { field = orderObj.expr; } else if (useBody) { - field = `body->>'${orderObj.field}'`; + const operator = jsonAsText ? '->>' : '->'; + field = `body${operator}'${orderObj.field}'`; } else if (orderObj.field) { - const parsed = parseKey(orderObj.field, () => {}); + const parsed = parseKey(orderObj.field, () => {}, jsonAsText); field = parsed.field; } diff --git a/lib/util/parse-key.js b/lib/util/parse-key.js index 1b510d856734f3a2d9e5df19f8556c616728f803..c54a7f46e1a0b4928a95ec2e0eb2fa28766c8b84 100644 --- a/lib/util/parse-key.js +++ b/lib/util/parse-key.js @@ -9,9 +9,10 @@ * @module util/parseKey * @param {String} key A reference to a database column. The field name may be quoted using double quotes to allow names which otherwise would not conform with database naming conventions. Optional components include, in order, [] and . notation to describe elements of a JSON field; ::type to describe a cast; and finally, an argument to the appendix function. * @param {Object} appendix A function which when invoked with an optional component of the key returns a value to be used later. So far used for operations (from {@linkcode where}) and ordering (from {@linkcode order}. + * @param {Boolean} jsonAsText A boolean to determine which JSON extraction operators to use * @return {Object} An object describing the parsed key. */ -exports = module.exports = function (key, appendix) { +exports = module.exports = function (key, appendix, jsonAsText = true) { key = key.trim(); const jsonShape = []; // describe a JSON path: true is a field, false an array index @@ -44,6 +45,7 @@ exports = module.exports = function (key, appendix) { // about type if (!hasCast) { hasCast = true; + jsonAsText = true; // Explicit casts must use as-text operators buffer = parsed[parsed.push([]) - 1]; } @@ -97,17 +99,19 @@ exports = module.exports = function (key, appendix) { if (jsonShape.length === 1) { elements.push(parsed.shift()); + const operator = jsonAsText ? '->>' : '->'; if (jsonShape[0]) { // object key - quotedField = `${quotedField}->>'${elements[0]}'`; + quotedField = `${quotedField}${operator}'${elements[0]}'`; } else { // array index - quotedField = `${quotedField}->>${elements[0]}`; + quotedField = `${quotedField}${operator}${elements[0]}`; } } else if (jsonShape.length > 0) { elements = parsed.splice(0, jsonShape.length); - quotedField = `${quotedField}#>>'{${elements.join(',')}}'`; + const operator = jsonAsText ? '#>>' : '#>'; + quotedField = `${quotedField}${operator}'{${elements.join(',')}}'`; } if (hasCast) { diff --git a/test/statement/order-by.js b/test/statement/order-by.js index e0d000dbc5b92ac2617781283dae61db998e0abe..8edc84b468e689fb8f88116089a56e75c3c8aca8 100644 --- a/test/statement/order-by.js +++ b/test/statement/order-by.js @@ -67,7 +67,7 @@ describe('orderBy', function () { assert.equal(orderBy([ {field: 'col1', direction: 'asc', type: 'int'}, {field: 'col2'} - ], true), `ORDER BY (body->>'col1')::int ASC,body->>'col2' ASC`); + ], true), `ORDER BY (body->>'col1')::int ASC,body->'col2' ASC`); }); it('should ignore useBody with exprs', function () { @@ -81,6 +81,6 @@ describe('orderBy', function () { {field: 'jsonobj.element', direction: 'asc'}, {field: 'jsonarray[1]', direction: 'desc'}, {field: 'complex.element[0].with.nested.properties', direction: 'asc'} - ]), `ORDER BY "jsonobj"->>'element' ASC,"jsonarray"->>1 DESC,"complex"#>>'{element,0,with,nested,properties}' ASC`); + ]), `ORDER BY "jsonobj"->'element' ASC,"jsonarray"->1 DESC,"complex"#>'{element,0,with,nested,properties}' ASC`); }); }); diff --git a/test/util/parse-key.js b/test/util/parse-key.js index 30de1072afcc26b7bd7ced09485b6d5986fa2001..87084d596a15e9ff776e49ca2b6d89d203f0d32c 100644 --- a/test/util/parse-key.js +++ b/test/util/parse-key.js @@ -74,6 +74,34 @@ describe('parseKey', function () { assert.equal(result.field, '"json"#>>\'{array,1,field,array,2}\''); assert.deepEqual(result.elements, ['array', '1', 'field', 'array', '2']); }); + + it('should format a shallow JSON path with as-text off', function () { + const result = parseKey('json.property', () => {}, false); + assert.equal(result.rawField, 'json'); + assert.equal(result.field, '"json"->\'property\''); + assert.deepEqual(result.elements, ['property']); + }); + + it('should format a JSON array path with as-text off', function () { + const result = parseKey('json[123]', () => {}, false); + assert.equal(result.rawField, 'json'); + assert.equal(result.field, '"json"->123'); + assert.deepEqual(result.elements, ['123']); + }); + + it('should format a deep JSON path with as-text off', function () { + const result = parseKey('json.outer.inner', () => {}, false); + assert.equal(result.rawField, 'json'); + assert.equal(result.field, '"json"#>\'{outer,inner}\''); + assert.deepEqual(result.elements, ['outer', 'inner']); + }); + + it('should force as-text on if JSON has cast', function () { + const result = parseKey('json.property::int', () => {}, false); + assert.equal(result.rawField, 'json'); + assert.equal(result.field, '("json"->>\'property\')::int'); + assert.deepEqual(result.elements, ['property']); + }); }); describe('operation appendices', function () {