Skip to content

Commit 8cc6b29

Browse files
authored
Merge pull request #417 from Tjuna/feature/setting-disable-default-sort
Feature: setting disable default sort
2 parents 754c1a0 + 7ac6634 commit 8cc6b29

File tree

3 files changed

+322
-0
lines changed

3 files changed

+322
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postg
145145
<td>Boolean</td>
146146
<td>Whether to try SSL/TLS to connect to server</td>
147147
</tr>
148+
<tr>
149+
<td>defaultIdSort</td>
150+
<td>Boolean/String</td>
151+
<td>Set to <code>false</code> to disable default sorting on <code>id</code> column(s). Set to <code>numericIdOnly</code> to only apply to IDs with a number type <code>id</code>.</td>
148152
</tbody>
149153
</table>
150154

lib/postgresql.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const ParameterizedSQL = SqlConnector.ParameterizedSQL;
1515
const util = require('util');
1616
const debug = require('debug')('loopback:connector:postgresql');
1717
const debugData = require('debug')('loopback:connector:postgresql:data');
18+
const debugSort = require('debug')('loopback:connector:postgresql:order');
1819
const Promise = require('bluebird');
1920

2021
/**
@@ -179,6 +180,95 @@ PostgreSQL.prototype.buildInsertReturning = function(model, data, options) {
179180
return 'RETURNING ' + idColumnNames.join(',');
180181
};
181182

183+
/**
184+
* Check if id types have a numeric type
185+
* @param {String} model name
186+
* @returns {Boolean}
187+
*/
188+
PostgreSQL.prototype.hasOnlyNumericIds = function(model) {
189+
const cols = this.getModelDefinition(model).properties;
190+
const idNames = this.idNames(model);
191+
const numericIds = idNames.filter(function(idName) {
192+
return cols[idName].type === Number;
193+
});
194+
195+
return numericIds.length == idNames.length;
196+
};
197+
198+
/**
199+
* Get default find sort policy
200+
* @param model
201+
*/
202+
PostgreSQL.prototype.getDefaultIdSortPolicy = function(model) {
203+
const modelClass = this._models[model];
204+
205+
if (modelClass.settings.hasOwnProperty('defaultIdSort')) {
206+
return modelClass.settings.defaultIdSort;
207+
}
208+
209+
if (this.settings.hasOwnProperty('defaultIdSort')) {
210+
return this.settings.defaultIdSort;
211+
}
212+
213+
return null;
214+
};
215+
216+
/**
217+
* Build a SQL SELECT statement
218+
* @param {String} model Model name
219+
* @param {Object} filter Filter object
220+
* @param {Object} options Options object
221+
* @returns {ParameterizedSQL} Statement object {sql: ..., params: ...}
222+
*/
223+
PostgreSQL.prototype.buildSelect = function(model, filter) {
224+
let sortById;
225+
226+
const sortPolicy = this.getDefaultIdSortPolicy(model);
227+
228+
switch (sortPolicy) {
229+
case 'numericIdOnly':
230+
sortById = this.hasOnlyNumericIds(model);
231+
break;
232+
case false:
233+
sortById = false;
234+
break;
235+
default:
236+
sortById = true;
237+
break;
238+
}
239+
240+
debugSort(model, 'sort policy:', sortPolicy, sortById);
241+
242+
if (sortById && !filter.order) {
243+
const idNames = this.idNames(model);
244+
if (idNames && idNames.length) {
245+
filter.order = idNames;
246+
}
247+
}
248+
249+
let selectStmt = new ParameterizedSQL('SELECT ' +
250+
this.buildColumnNames(model, filter) +
251+
' FROM ' + this.tableEscaped(model));
252+
253+
if (filter) {
254+
if (filter.where) {
255+
const whereStmt = this.buildWhere(model, filter.where);
256+
selectStmt.merge(whereStmt);
257+
}
258+
259+
if (filter.order) {
260+
selectStmt.merge(this.buildOrderBy(model, filter.order));
261+
}
262+
263+
if (filter.limit || filter.skip || filter.offset) {
264+
selectStmt = this.applyPagination(
265+
model, selectStmt, filter,
266+
);
267+
}
268+
}
269+
return this.parameterize(selectStmt);
270+
};
271+
182272
PostgreSQL.prototype.buildInsertDefaultValues = function(model, data, options) {
183273
return 'DEFAULT VALUES';
184274
};

test/postgresql.order.test.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Copyright IBM Corp. 2020. All Rights Reserved.
2+
// Node module: loopback-connector-postgresql
3+
// This file is licensed under the Artistic License 2.0.
4+
// License text available at https://opensource.org/licenses/Artistic-2.0
5+
6+
'use strict';
7+
8+
require('./init');
9+
const should = require('should');
10+
const async = require('async');
11+
12+
let db,
13+
PostWithDefaultIdSort,
14+
PostWithDisabledDefaultIdSort,
15+
PostWithNumericDefaultIdSort,
16+
PostWithNumericStringDefaultIdSort;
17+
18+
describe('Order settings', function() {
19+
before(function() {
20+
db = global.getSchema();
21+
});
22+
23+
before(function(done) {
24+
PostWithDefaultIdSort = db.define(
25+
'PostWithDefaultIdSort',
26+
{
27+
id: {type: Number, id: true},
28+
title: {type: String, length: 255, index: true},
29+
content: {type: String},
30+
},
31+
{
32+
defaultIdSort: true,
33+
},
34+
);
35+
36+
PostWithDisabledDefaultIdSort = db.define(
37+
'PostWithDisabledDefaultIdSort',
38+
{
39+
id: {type: Number, id: true},
40+
title: {type: String, length: 255, index: true},
41+
content: {type: String},
42+
},
43+
{
44+
defaultIdSort: false,
45+
},
46+
);
47+
48+
PostWithNumericDefaultIdSort = db.define(
49+
'PostWithNumericDefaultIdSort',
50+
{
51+
id: {type: Number, id: true},
52+
title: {type: String, length: 255, index: true},
53+
content: {type: String},
54+
},
55+
{
56+
defaultIdSort: 'numericIdOnly',
57+
},
58+
);
59+
60+
PostWithNumericStringDefaultIdSort = db.define(
61+
'PostWithNumericStringDefaultIdSort',
62+
{
63+
id: {type: String, id: true},
64+
title: {type: String, length: 255, index: true},
65+
content: {type: String},
66+
},
67+
{
68+
defaultIdSort: 'numericIdOnly',
69+
},
70+
);
71+
72+
done();
73+
});
74+
75+
before(function(done) {
76+
db.automigrate([
77+
'PostWithDefaultIdSort',
78+
'PostWithDisabledDefaultIdSort',
79+
'PostWithNumericDefaultIdSort',
80+
'PostWithNumericStringDefaultIdSort'],
81+
done);
82+
});
83+
84+
after(function(done) {
85+
async.parallel([
86+
cb => PostWithDefaultIdSort.destroyAll(cb),
87+
cb => PostWithDisabledDefaultIdSort.destroyAll(cb),
88+
cb => PostWithNumericDefaultIdSort.destroyAll(cb),
89+
cb => PostWithNumericStringDefaultIdSort.destroyAll(cb),
90+
], done);
91+
});
92+
93+
it('builds base query', function(done) {
94+
const res = db.connector.buildSelect('PostWithDefaultIdSort', {});
95+
const sql = res.sql;
96+
97+
sql.should.be.equal('SELECT "id","title","content" FROM "public"."postwithdefaultidsort" ORDER BY "id"');
98+
done();
99+
});
100+
101+
it('builds non-ordering base query', function(done) {
102+
const res = db.connector.buildSelect('PostWithDisabledDefaultIdSort', {});
103+
const sql = res.sql;
104+
105+
sql.should.be.equal('SELECT "id","title","content" FROM "public"."postwithdisableddefaultidsort"');
106+
done();
107+
});
108+
109+
it('builds ordering query based on numericIdOnly setting', function(done) {
110+
const res = db.connector.buildSelect('PostWithNumericDefaultIdSort', {});
111+
const sql = res.sql;
112+
113+
sql.should.be.equal('SELECT "id","title","content" FROM "public"."postwithnumericdefaultidsort" ORDER BY "id"');
114+
done();
115+
});
116+
117+
it('builds non-ordering query based on numericIdOnly setting', function(done) {
118+
const res = db.connector.buildSelect('PostWithNumericStringDefaultIdSort', {});
119+
const sql = res.sql;
120+
121+
sql.should.be.equal('SELECT "id","title","content" FROM "public"."postwithnumericstringdefaultidsort"');
122+
done();
123+
});
124+
125+
it('should return results ordered by id',
126+
function(done) {
127+
PostWithDefaultIdSort.create({id: 5, title: 'c', content: 'CCC'}, function(err, post) {
128+
PostWithDefaultIdSort.create({id: 4, title: 'd', content: 'DDD'}, function(err, post) {
129+
PostWithDefaultIdSort.find({}, function(err, posts) {
130+
should.not.exist(err);
131+
posts.length.should.be.equal(2);
132+
posts[0].id.should.be.equal(4);
133+
134+
PostWithDefaultIdSort.find({limit: 1, offset: 0}, function(err, posts) {
135+
should.not.exist(err);
136+
posts.length.should.be.equal(1);
137+
posts[0].id.should.be.equal(4);
138+
139+
PostWithDefaultIdSort.find({limit: 1, offset: 1}, function(err, posts) {
140+
should.not.exist(err);
141+
posts.length.should.be.equal(1);
142+
posts[0].id.should.be.equal(5);
143+
done();
144+
});
145+
});
146+
});
147+
});
148+
});
149+
});
150+
151+
it('should return unordered results',
152+
function(done) {
153+
PostWithDisabledDefaultIdSort.create({id: 2, title: 'c', content: 'CCC'}, function(err, post) {
154+
PostWithDisabledDefaultIdSort.create({id: 1, title: 'd', content: 'DDD'}, function(err, post) {
155+
PostWithDisabledDefaultIdSort.find({}, function(err, posts) {
156+
should.not.exist(err);
157+
posts.length.should.be.equal(2);
158+
posts[0].id.should.be.equal(2);
159+
160+
PostWithDisabledDefaultIdSort.find({limit: 1, offset: 0}, function(err, posts) {
161+
should.not.exist(err);
162+
posts.length.should.be.equal(1);
163+
posts[0].id.should.be.equal(2);
164+
165+
PostWithDisabledDefaultIdSort.find({limit: 1, offset: 1}, function(err, posts) {
166+
should.not.exist(err);
167+
posts.length.should.be.equal(1);
168+
posts[0].id.should.be.equal(1);
169+
done();
170+
});
171+
});
172+
});
173+
});
174+
});
175+
});
176+
177+
it('Should return ordered results by numeric id by default',
178+
function(done) {
179+
PostWithNumericDefaultIdSort.create({id: 12, title: 'c', content: 'CCC'}, function(err, post) {
180+
PostWithNumericDefaultIdSort.create({id: 11, title: 'd', content: 'DDD'}, function(err, post) {
181+
PostWithNumericDefaultIdSort.find({}, function(err, posts) {
182+
should.not.exist(err);
183+
posts.length.should.be.equal(2);
184+
posts[0].id.should.be.equal(11);
185+
186+
PostWithNumericDefaultIdSort.find({limit: 1, offset: 0}, function(err, posts) {
187+
should.not.exist(err);
188+
posts.length.should.be.equal(1);
189+
posts[0].id.should.be.equal(11);
190+
191+
PostWithNumericDefaultIdSort.find({limit: 1, offset: 1}, function(err, posts) {
192+
should.not.exist(err);
193+
posts.length.should.be.equal(1);
194+
posts[0].id.should.be.equal(12);
195+
done();
196+
});
197+
});
198+
});
199+
});
200+
});
201+
});
202+
203+
it('should return unordered results because of string type id',
204+
function(done) {
205+
PostWithNumericStringDefaultIdSort.create({id: 'b', title: 'c', content: 'CCC'}, function(err, post) {
206+
PostWithNumericStringDefaultIdSort.create({id: 'a', title: 'd', content: 'DDD'}, function(err, post) {
207+
PostWithNumericStringDefaultIdSort.find({}, function(err, posts) {
208+
should.not.exist(err);
209+
posts.length.should.be.equal(2);
210+
posts[0].id.should.be.equal('b');
211+
212+
PostWithNumericStringDefaultIdSort.find({limit: 1, offset: 0}, function(err, posts) {
213+
should.not.exist(err);
214+
posts.length.should.be.equal(1);
215+
posts[0].id.should.be.equal('b');
216+
217+
PostWithNumericStringDefaultIdSort.find({limit: 1, offset: 1}, function(err, posts) {
218+
should.not.exist(err);
219+
posts.length.should.be.equal(1);
220+
posts[0].id.should.be.equal('a');
221+
done();
222+
});
223+
});
224+
});
225+
});
226+
});
227+
});
228+
});

0 commit comments

Comments
 (0)