Skip to content

Commit dce7bad

Browse files
author
Laszlo Radics
committed
feat: add queryFirst, queryOne queries which return only one result
add distinct, forUpdate query options
1 parent aaa14c4 commit dce7bad

File tree

6 files changed

+85
-15
lines changed

6 files changed

+85
-15
lines changed

docs/API/PgDb.md

+13
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ Executes an arbitrary sql string with parameters / named parameters.
8787
let res1 = await schema.query('SELECT MAX(point) from game1.scores WHERE name=$1 ', ['player1']);
8888
let res2 = await schema.query('SELECT MAX(point) from !:schema.scores WHERE name=:name ', {schema:'game1', name:'player1'});
8989
```
90+
---
91+
##queryFirst
92+
<span class="def"><span class="func">queryFirst</span>(sql:<span class="type">string</span>, params?:<span class="type">any[]|{}</span>, options?:<span class="type">SqlQueryOptions</span>):Promise&lt;<span class="type">any</span>&gt;</span>
93+
<a name="query"></a>
94+
95+
Executes an arbitrary sql string with parameters / named parameters. Return the first record.
96+
97+
---
98+
##queryOne
99+
<span class="def"><span class="func">query</span>(sql:<span class="type">string</span>, params?:<span class="type">any[]|{}</span>, options?:<span class="type">SqlQueryOptions</span>):Promise&lt;<span class="type">any</span>&gt;</span>
100+
<a name="query"></a>
101+
102+
Executes an arbitrary sql string with parameters / named parameters. Return the first record, throw Error if there are more.
90103

91104
---
92105
## queryOneField

docs/API/QueryOptions.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ interface QueryOptions {
1414
limit?:number;
1515
offset?: number;
1616
orderBy?: string|string[]|{[fieldName:string]:'asc'|'desc'};//free text or column list
17-
groupBy?:string|string[];//free text or column list
18-
fields?: string|string[];//free text or column list
19-
skipUndefined?:boolean; //if there is an undefined value in the conditions, shall it be skipped. Default raise error.
17+
groupBy?:string|string[]; //free text or column list
18+
fields?: string|string[]; //free text or column list
19+
skipUndefined?:boolean; //if there is an undefined value in the conditions, shall it be skipped. Default raise error.
2020
logger?:PgDbLogger;
21+
distinct?: boolean; // SELECT DISTINCT statement
22+
forUpdate?: boolean; // FOR UPDATE statement
2123
}
2224
```
2325
where orderBy/groupBy/fields can be either an array (in that case will get quotation if needed)

src/pgTable.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import generateWhere from "./queryWhere";
44
import {PgSchema} from "./pgSchema";
55
import {pgUtils} from "./pgUtils";
66
import * as _ from 'lodash';
7+
import * as stream from "stream";
78

89
const util = require('util');
910

@@ -87,8 +88,8 @@ export class PgTable<T> extends QueryAble {
8788

8889
async insertAndGet(records: T[], options?: InsertOption & Return): Promise<T[]>
8990
async insertAndGet(records: T, options?: InsertOption & Return): Promise<T>
90-
async insertAndGet(records: any, options?: any): Promise<any> {
91-
var returnSingle = false;
91+
async insertAndGet(records: any, options?: InsertOption & Return): Promise<any> {
92+
let returnSingle = false;
9293
options = options || {};
9394

9495
if (!records) {
@@ -201,11 +202,10 @@ export class PgTable<T> extends QueryAble {
201202
}
202203

203204
async find(conditions: { [k: string]: any }, options?: QueryOptions): Promise<T[]>
204-
async find(conditions: { [k: string]: any }, options?: QueryOptions & Stream): Promise<{ on: any }>
205+
async find(conditions: { [k: string]: any }, options?: QueryOptions & Stream): Promise<stream.Readable>
205206
async find(conditions: { [k: string]: any }, options?: any): Promise<any> {
206207
options = options || {};
207208
options.skipUndefined = options.skipUndefined === true || (options.skipUndefined === undefined && ['all', 'select'].indexOf(this.db.config.skipUndefined) > -1);
208-
209209
let where = _.isEmpty(conditions) ? {
210210
where: " ",
211211
params: null
@@ -216,15 +216,15 @@ export class PgTable<T> extends QueryAble {
216216

217217

218218
async findWhere(where: string, params: any[] | {}, options?: QueryOptions): Promise<T[]>
219-
async findWhere(where: string, params: any[] | {}, options?: QueryOptions & Stream): Promise<any> //Readable
219+
async findWhere(where: string, params: any[] | {}, options?: QueryOptions & Stream): Promise<stream.Readable>
220220
async findWhere(where: string, params: any, options?: any): Promise<any> {
221221
options = options || {};
222222
let sql = `SELECT ${pgUtils.processQueryFields(options)} FROM ${this.qualifiedName} WHERE ${where} ${pgUtils.processQueryOptions(options)}`;
223223
return options.stream ? this.queryAsStream(sql, params, options) : this.query(sql, params, options);
224224
}
225225

226226
public async findAll(options?: QueryOptions): Promise<T[]>
227-
public async findAll(options?: QueryOptions & Stream): Promise<any> //Readable
227+
public async findAll(options?: QueryOptions & Stream): Promise<stream.Readable>
228228
public async findAll(options?: any): Promise<any> {
229229
options = options || {};
230230
let sql = `SELECT ${pgUtils.processQueryFields(options)} FROM ${this.qualifiedName} ${pgUtils.processQueryOptions(options)}`;

src/pgUtils.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ export let pgUtils = {
1212
},
1313

1414
processQueryFields(options: QueryOptions): string {
15+
let s = options && options.distinct ? ' DISTINCT ' : ' ';
1516
if (options && options.fields) {
1617
if (Array.isArray(options.fields)) {
17-
return ' ' + options.fields.map(pgUtils.quoteField).join(',');
18+
return s + options.fields.map(pgUtils.quoteField).join(', ');
1819
} else {
19-
return ' ' + options.fields;
20+
return s + options.fields;
2021
}
2122
} else {
22-
return ' *';
23+
return s + ' *';
2324
}
2425
},
2526

@@ -97,6 +98,9 @@ export let pgUtils = {
9798
if (options.offset) {
9899
sql += util.format(' OFFSET %d', options.offset);
99100
}
101+
if (options.forUpdate){
102+
sql += ' FOR UPDATE';
103+
}
100104
return sql;
101105
},
102106

src/queryAble.ts

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export interface QueryOptions {
1313
groupBy?: string | string[];//free text or column list
1414
fields?: string | string[];//free text or column list
1515
logger?: PgDbLogger;
16+
forUpdate?: boolean;
17+
distinct?: boolean;
1618
skipUndefined?: boolean;
1719
}
1820

@@ -254,6 +256,19 @@ export class QueryAble {
254256
}
255257
}
256258

259+
async queryOne(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
260+
let res = await this.query(sql, params, options);
261+
if (res.length > 1) {
262+
throw new Error('More then one rows exists');
263+
}
264+
return res[0];
265+
}
266+
267+
async queryFirst(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
268+
let res = await this.query(sql, params, options);
269+
return res[0];
270+
}
271+
257272
/** @return one record's one field */
258273
async queryOneField(sql: string, params?: any[] | {}, options?: SqlQueryOptions): Promise<any> {
259274
let res = await this.query(sql, params, options);

src/test/pgDbSpec.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ function parseComplexType(str) {
6262
return valList;
6363
}
6464

65+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 800000;
6566

6667
describe("pgdb", () => {
6768
let pgdb: PgDb;
@@ -70,8 +71,6 @@ describe("pgdb", () => {
7071
let tableGroups: PgTable<any>;
7172

7273
beforeAll(w(async () => {
73-
jasmine.DEFAULT_TIMEOUT_INTERVAL = 800000;
74-
7574
/**
7675
* Using environment variables, e.g.
7776
* PGUSER (defaults USER env var, so optional)
@@ -767,9 +766,46 @@ describe("pgdb", () => {
767766
let list = ["'A'", '"A"', '//', '\\', '""', "''", '--', '/*', '<!--'];
768767
await table.insert({name: 'A', textList: list});
769768
let rec: any = await table.findOne({name: 'A'});
770-
console.log(list + '\n' + rec.textList);
769+
// console.log(list + '\n' + rec.textList);
771770
let isDifferent = list.some((v, i) => rec.textList[i] !== v);
772771
expect(isDifferent).toBeFalsy();
773772
}));
774773

774+
it("Testing distinct", w(async () => {
775+
await table.insert({name: 'A', aCategory: 'A'});
776+
await table.insert({name: 'B', aCategory: 'A'});
777+
let recs = await table.find({aCategory: 'A'}, {fields: ['aCategory'], distinct: true});
778+
expect(recs.length).toEqual(1);
779+
}));
780+
781+
it("Testing queryOne", w(async () => {
782+
await table.insert({name: 'A', aCategory: 'A'});
783+
await table.insert({name: 'B', aCategory: 'A'});
784+
try {
785+
let recs = await table.queryOne(`SELECT * FROM ${table} WHERE "aCategory" = 'A'`);
786+
expect(false).toBeTruthy();
787+
} catch (e) {
788+
expect('' + e).toEqual("Error: More then one rows exists");
789+
}
790+
}));
791+
it("Testing queryFirst", w(async () => {
792+
await table.insert({name: 'A', aCategory: 'A'});
793+
await table.insert({name: 'B', aCategory: 'A'});
794+
let rec = await table.queryFirst(`SELECT * FROM ${table} WHERE "aCategory" = 'A'`);
795+
expect(rec.aCategory).toEqual('A');
796+
}));
797+
798+
it("Testing forUpdate", w(async () => {
799+
await table.insert({name: 'A', aCategory: 'A'});
800+
let pgdb1 = await table.db.transactionBegin();
801+
await pgdb1.tables['users'].find({aCategory: 'A'},{forUpdate: true});
802+
let p = table.update({aCategory: 'A'}, {name: 'C'});
803+
await new Promise(resolve => setTimeout(resolve, 500));
804+
await pgdb1.tables['users'].update({aCategory: 'A'}, {name: 'B'});
805+
await pgdb1.transactionCommit();
806+
await p;
807+
let rec = await table.findFirst({aCategory: 'A'});
808+
expect(rec.name).toEqual('C');
809+
}));
810+
775811
});

0 commit comments

Comments
 (0)