pogi 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +35 -0
- package/CHANGELOG.md +277 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/docs/API/PgDb.md +218 -0
- package/docs/API/PgSchema.md +91 -0
- package/docs/API/PgTable.md +365 -0
- package/docs/API/QueryOptions.md +77 -0
- package/docs/API/condition.md +133 -0
- package/docs/connection.md +91 -0
- package/docs/css/docs.css +164 -0
- package/docs/executingSqlFile.md +44 -0
- package/docs/faq.md +15 -0
- package/docs/functions.md +19 -0
- package/docs/generatingInterfaceForTables.md +35 -0
- package/docs/index.md +48 -0
- package/docs/logger.md +40 -0
- package/docs/mappingDatabaseTypes.md +89 -0
- package/docs/notification.md +19 -0
- package/docs/pitfalls.md +73 -0
- package/docs/streams.md +68 -0
- package/docs/transaction.md +65 -0
- package/lib/bin/generateInterface.d.ts +1 -0
- package/lib/bin/generateInterface.js +53 -0
- package/lib/bin/generateInterface.js.map +1 -0
- package/lib/connectionOptions.d.ts +25 -0
- package/lib/connectionOptions.js +3 -0
- package/lib/connectionOptions.js.map +1 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +10 -0
- package/lib/index.js.map +1 -0
- package/lib/pgConverters.d.ts +10 -0
- package/lib/pgConverters.js +66 -0
- package/lib/pgConverters.js.map +1 -0
- package/lib/pgDb.d.ts +86 -0
- package/lib/pgDb.js +745 -0
- package/lib/pgDb.js.map +1 -0
- package/lib/pgDbLogger.d.ts +5 -0
- package/lib/pgDbLogger.js +3 -0
- package/lib/pgDbLogger.js.map +1 -0
- package/lib/pgDbOperators.d.ts +113 -0
- package/lib/pgDbOperators.js +44 -0
- package/lib/pgDbOperators.js.map +1 -0
- package/lib/pgSchema.d.ts +16 -0
- package/lib/pgSchema.js +16 -0
- package/lib/pgSchema.js.map +1 -0
- package/lib/pgTable.d.ts +131 -0
- package/lib/pgTable.js +322 -0
- package/lib/pgTable.js.map +1 -0
- package/lib/pgUtils.d.ts +31 -0
- package/lib/pgUtils.js +157 -0
- package/lib/pgUtils.js.map +1 -0
- package/lib/queryAble.d.ts +76 -0
- package/lib/queryAble.js +330 -0
- package/lib/queryAble.js.map +1 -0
- package/lib/queryWhere.d.ts +8 -0
- package/lib/queryWhere.js +249 -0
- package/lib/queryWhere.js.map +1 -0
- package/mkdocs.yml +25 -0
- package/package.json +65 -0
- package/spec/resources/init.sql +122 -0
- package/spec/resources/throw_exception.sql +5 -0
- package/spec/resources/tricky.sql +13 -0
- package/spec/run.js +5 -0
- package/spec/support/jasmine.json +9 -0
- package/src/bin/generateInterface.ts +54 -0
- package/src/connectionOptions.ts +42 -0
- package/src/index.ts +6 -0
- package/src/pgConverters.ts +55 -0
- package/src/pgDb.ts +820 -0
- package/src/pgDbLogger.ts +13 -0
- package/src/pgDbOperators.ts +62 -0
- package/src/pgSchema.ts +15 -0
- package/src/pgTable.ts +401 -0
- package/src/pgUtils.ts +176 -0
- package/src/queryAble.ts +393 -0
- package/src/queryWhere.ts +326 -0
- package/src/test/pgDbOperatorSpec.ts +492 -0
- package/src/test/pgDbSpec.ts +1339 -0
- package/src/test/pgServiceRestartTest.ts +1500 -0
- package/src/tsconfig.json +33 -0
- package/utils_sql/lower.sql +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* log will get 3 parameters:
|
|
3
|
+
* sql -> the query
|
|
4
|
+
* parameters -> parameters for the query
|
|
5
|
+
* poolId -> the id of the connection
|
|
6
|
+
*
|
|
7
|
+
* paramSanitizer - optional function to remove parameters from the query for security reason (e.g. email / password or similar)
|
|
8
|
+
*/
|
|
9
|
+
export interface PgDbLogger {
|
|
10
|
+
log: Function;
|
|
11
|
+
error: Function;
|
|
12
|
+
paramSanitizer?: Function;
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const util = require("util");
|
|
2
|
+
|
|
3
|
+
function escapeForLike(s) {
|
|
4
|
+
return s.replace(/([\\%_])/g,'\\$1');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
// lowercase comparison
|
|
9
|
+
'=*': {operator: '=', mutator: (s:string) => s.toLocaleLowerCase(), fieldMutator: s => util.format('LOWER("%s")',s)},
|
|
10
|
+
|
|
11
|
+
// caseless contains for string
|
|
12
|
+
'icontains': {operator: 'ILIKE', mutator: s => '%' + escapeForLike(s) + '%'},
|
|
13
|
+
|
|
14
|
+
// contains for array
|
|
15
|
+
//'acontains': value = ANY("columnName")
|
|
16
|
+
|
|
17
|
+
// basic comparison
|
|
18
|
+
'=': {operator: '='},
|
|
19
|
+
'!': {operator: '<>'},
|
|
20
|
+
'>': {operator: '>'},
|
|
21
|
+
'<': {operator: '<'},
|
|
22
|
+
'>=': {operator: '>='},
|
|
23
|
+
'<=': {operator: '<='},
|
|
24
|
+
'!=': {operator: '<>'},
|
|
25
|
+
'<>': {operator: '<>'},
|
|
26
|
+
'is not': {operator: 'IS NOT'},
|
|
27
|
+
|
|
28
|
+
// free text search
|
|
29
|
+
'@@': {operator: '@@'}, //value can be {lang:string, query:string} or simply string (defaults to english)
|
|
30
|
+
|
|
31
|
+
// jsonb / array
|
|
32
|
+
'@>': {operator: '@>'}, //contains ARRAY[1,4,3] @> ARRAY[3,1] => true
|
|
33
|
+
'<@': {operator: '<@'}, //is contained by ARRAY[2,7] <@ ARRAY[1,7,4,2,6] => true
|
|
34
|
+
'&&': {operator: '&&'}, //overlap (have elements in common) ARRAY[1,4,3] && ARRAY[2,1] => true
|
|
35
|
+
'&&*': {operator: '&&', mutator: (s:string) => s.toLocaleLowerCase(), fieldMutator: f => util.format('LOWER("%s")', f)},
|
|
36
|
+
|
|
37
|
+
// jsonb
|
|
38
|
+
'?': {operator: '?'}, //exists key
|
|
39
|
+
'?|': {operator: '?|'}, //exists any keys
|
|
40
|
+
'?&': {operator: '?&'}, //exists all keys
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// pattern matching
|
|
44
|
+
'~~': {operator: 'LIKE'},
|
|
45
|
+
'like': {operator: 'LIKE'},
|
|
46
|
+
'!~~': {operator: 'NOT LIKE'},
|
|
47
|
+
'not like': {operator: 'NOT LIKE'},
|
|
48
|
+
'~~*': {operator: 'ILIKE'},
|
|
49
|
+
'ilike': {operator: 'ILIKE'},
|
|
50
|
+
'!~~*': {operator: 'NOT ILIKE'},
|
|
51
|
+
'not ilike': {operator: 'NOT ILIKE'},
|
|
52
|
+
'similar to': {operator: 'SIMILAR TO'},
|
|
53
|
+
'not similar to': {operator: 'NOT SIMILAR TO'},
|
|
54
|
+
// regexp
|
|
55
|
+
'~': {operator: '~'},
|
|
56
|
+
'!~': {operator: '!~'},
|
|
57
|
+
'~*': {operator: '~*'}, //Matches regular expression, case insensitive
|
|
58
|
+
'!~*': {operator: '!~*'},
|
|
59
|
+
// distinct
|
|
60
|
+
'is distinct from': {operator: 'IS DISTINCT FROM'},
|
|
61
|
+
'is not distinct from': {operator: 'IS NOT DISTINCT FROM'}
|
|
62
|
+
};
|
package/src/pgSchema.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {QueryAble} from "./queryAble";
|
|
2
|
+
import {PgDb} from "./pgDb";
|
|
3
|
+
import {PgTable} from "./pgTable";
|
|
4
|
+
|
|
5
|
+
export class PgSchema extends QueryAble {
|
|
6
|
+
schema:PgSchema;
|
|
7
|
+
tables:{[name:string]:PgTable<any>} = {};
|
|
8
|
+
fn: {[name:string]:(...any)=>any} = {};
|
|
9
|
+
[name:string]:any|PgTable<any>;
|
|
10
|
+
|
|
11
|
+
constructor(public db:PgDb, public schemaName:string) {
|
|
12
|
+
super();
|
|
13
|
+
this.schema = this;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/pgTable.ts
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {QueryAble, QueryOptions} from "./queryAble";
|
|
2
|
+
import {PgDb, FieldType} from "./pgDb";
|
|
3
|
+
import {PgDbLogger} from "./pgDbLogger"
|
|
4
|
+
import generateWhere from "./queryWhere";
|
|
5
|
+
import {PgSchema} from "./pgSchema";
|
|
6
|
+
import {pgUtils} from "./pgUtils";
|
|
7
|
+
import * as _ from 'lodash';
|
|
8
|
+
import * as stream from "stream";
|
|
9
|
+
|
|
10
|
+
const util = require('util');
|
|
11
|
+
|
|
12
|
+
export interface InsertOption {
|
|
13
|
+
logger?: PgDbLogger;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Return {
|
|
17
|
+
return?: string[] | '*';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UpdateDeleteOption {
|
|
21
|
+
skipUndefined?: boolean;
|
|
22
|
+
logger?: PgDbLogger;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UpsertOption {
|
|
26
|
+
constraint?: string,
|
|
27
|
+
columns?: string[],
|
|
28
|
+
logger?: PgDbLogger;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CountOption {
|
|
32
|
+
skipUndefined?: boolean;
|
|
33
|
+
logger?: PgDbLogger;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Stream {
|
|
37
|
+
stream: true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface TruncateOptions {
|
|
41
|
+
restartIdentity?: boolean,
|
|
42
|
+
cascade?: boolean,
|
|
43
|
+
logger?: PgDbLogger;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class PgTable<T> extends QueryAble {
|
|
47
|
+
qualifiedName: string;
|
|
48
|
+
pkey: string;
|
|
49
|
+
db: PgDb;
|
|
50
|
+
fieldTypes: { [index: string]: FieldType }; //written directly
|
|
51
|
+
|
|
52
|
+
constructor(public schema: PgSchema, protected desc: { name: string, pkey?:string, schema: string }, fieldTypes = {}) {
|
|
53
|
+
super();
|
|
54
|
+
this.db = schema.db;
|
|
55
|
+
this.qualifiedName = util.format('"%s"."%s"', desc.schema, desc.name);
|
|
56
|
+
this.pkey = desc.pkey || desc.name + "_pkey"; //poor man's pkey (could be queried by why?)
|
|
57
|
+
this.fieldTypes = fieldTypes;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toString() {
|
|
61
|
+
return this.qualifiedName;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* If you dont want to use the result set the options.return to false
|
|
66
|
+
* by default it is true. Also can set it to the fields that need to be returned,
|
|
67
|
+
* e.g.:
|
|
68
|
+
*
|
|
69
|
+
* let res = await table.insert([{username:'anonymous'},{username:'anonymous2'}], {return:['id']})
|
|
70
|
+
* res; // [{id:1},{id:2}]
|
|
71
|
+
*
|
|
72
|
+
* let res = await table.insert({username:'anonymous'}, {return:false})
|
|
73
|
+
* res; // void
|
|
74
|
+
*
|
|
75
|
+
* let res = await table.insert({username:'anonymous'})
|
|
76
|
+
* res; // {id:1, name:'anonymous', created:'...'}
|
|
77
|
+
*
|
|
78
|
+
*/
|
|
79
|
+
async insert(records: T[], options?: InsertOption): Promise<number>
|
|
80
|
+
async insert(records: T, options?: InsertOption): Promise<number>
|
|
81
|
+
async insert(records: any, options?: any): Promise<any> {
|
|
82
|
+
options = options || {};
|
|
83
|
+
|
|
84
|
+
if (!records) {
|
|
85
|
+
throw new Error("insert should be called with data");
|
|
86
|
+
} else if (!Array.isArray(records)) {
|
|
87
|
+
records = [records];
|
|
88
|
+
} else if (records.length === 0) {
|
|
89
|
+
return 0; // just return empty arrays so bulk inserting variable-length lists is more friendly
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let {sql, parameters} = this.getInsertQuery(records);
|
|
93
|
+
sql = "WITH __RESULT as ( " + sql + " RETURNING 1) SELECT SUM(1) FROM __RESULT";
|
|
94
|
+
let result = await this.query(sql, parameters, {logger: options.logger});
|
|
95
|
+
return result[0].sum;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async insertAndGet(records: T[], options?: InsertOption & Return): Promise<T[]>
|
|
99
|
+
async insertAndGet(records: T, options?: InsertOption & Return): Promise<T>
|
|
100
|
+
async insertAndGet(records: any, options?: InsertOption & Return): Promise<any> {
|
|
101
|
+
let returnSingle = false;
|
|
102
|
+
options = options || {};
|
|
103
|
+
|
|
104
|
+
if (!records) {
|
|
105
|
+
throw new Error("insert should be called with data");
|
|
106
|
+
} else if (!Array.isArray(records)) {
|
|
107
|
+
returnSingle = true;
|
|
108
|
+
records = [records];
|
|
109
|
+
} else if (records.length === 0) {
|
|
110
|
+
return []; // just return empty arrays so bulk inserting variable-length lists is more friendly
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let {sql, parameters} = this.getInsertQuery(records);
|
|
114
|
+
|
|
115
|
+
sql += " RETURNING " + (options && options.return && Array.isArray(options.return) ? options.return.map(pgUtils.quoteField).join(',') : '*');
|
|
116
|
+
|
|
117
|
+
let result = await this.query(sql, parameters, {logger: options.logger});
|
|
118
|
+
if (options.return && options.return.length == 0) {
|
|
119
|
+
return new Array(returnSingle ? 1 : records.length).fill({});
|
|
120
|
+
}
|
|
121
|
+
if (returnSingle) {
|
|
122
|
+
return result[0];
|
|
123
|
+
} else {
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
async updateOne(conditions: { [k: string]: any }, fields: { [k: string]: any }, options?: UpdateDeleteOption): Promise<number> {
|
|
129
|
+
let affected = await this.update(conditions, fields, options);
|
|
130
|
+
if (affected > 1) {
|
|
131
|
+
throw new Error('More then one record has been updated!');
|
|
132
|
+
}
|
|
133
|
+
return affected;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async updateAndGetOne(conditions: { [k: string]: any }, fields: { [k: string]: any }, options?: UpdateDeleteOption & Return): Promise<T> {
|
|
137
|
+
let result = await this.updateAndGet(conditions, fields, options);
|
|
138
|
+
if (result.length > 1) {
|
|
139
|
+
throw new Error('More then one record has been updated!');
|
|
140
|
+
}
|
|
141
|
+
return result[0];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async update(conditions: { [k: string]: any }, fields: { [k: string]: any }, options?: UpdateDeleteOption): Promise<number> {
|
|
145
|
+
let {sql, parameters} = this.getUpdateQuery(conditions, fields, options);
|
|
146
|
+
sql = "WITH __RESULT as ( " + sql + " RETURNING 1) SELECT SUM(1) FROM __RESULT";
|
|
147
|
+
let res = await this.query(sql, parameters, options);
|
|
148
|
+
return res[0].sum;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
async updateAndGet(conditions: { [k: string]: any }, fields: { [k: string]: any }, options?: UpdateDeleteOption & Return): Promise<T[]> {
|
|
152
|
+
let {sql, parameters} = this.getUpdateQuery(conditions, fields, options);
|
|
153
|
+
sql += " RETURNING " + (options && options.return && Array.isArray(options.return) ? options.return.map(pgUtils.quoteField).join(',') : '*');
|
|
154
|
+
return this.query(sql, parameters, options);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* columnsOrConstraintName is by default the primary key
|
|
159
|
+
*/
|
|
160
|
+
async upsert(record: T, options?: UpsertOption): Promise<number> {
|
|
161
|
+
options = options || {};
|
|
162
|
+
if (!record) {
|
|
163
|
+
throw new Error("insert should be called with data");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let {sql, parameters} = this.getUpsertQuery(record, options);
|
|
167
|
+
sql = "WITH __RESULT as ( " + sql + " RETURNING 1) SELECT SUM(1) FROM __RESULT";
|
|
168
|
+
let result = await this.query(sql, parameters, {logger: options.logger});
|
|
169
|
+
return result[0].sum;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* columnsOrConstraintName is by default the primary key
|
|
174
|
+
*/
|
|
175
|
+
async upsertAndGet(record: T, options?: UpsertOption & Return): Promise<T> {
|
|
176
|
+
options = options || {};
|
|
177
|
+
if (!record) {
|
|
178
|
+
throw new Error("insert should be called with data");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let {sql, parameters} = this.getUpsertQuery(record, options);
|
|
182
|
+
sql += " RETURNING " + (options && options.return && Array.isArray(options.return) ? options.return.map(pgUtils.quoteField).join(',') : '*');
|
|
183
|
+
|
|
184
|
+
let result = await this.query(sql, parameters, {logger: options.logger});
|
|
185
|
+
|
|
186
|
+
if (options.return && options.return.length == 0) {
|
|
187
|
+
return <T>{};
|
|
188
|
+
}
|
|
189
|
+
return result[0];
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
async delete(conditions: { [k: string]: any }, options?: UpdateDeleteOption): Promise<number> {
|
|
193
|
+
let {sql, parameters} = this.getDeleteQuery(conditions, options);
|
|
194
|
+
sql = "WITH __RESULT as ( " + sql + " RETURNING 1) SELECT SUM(1) FROM __RESULT";
|
|
195
|
+
let res = await this.query(sql, parameters, options);
|
|
196
|
+
return res[0].sum;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async deleteOne(conditions: { [k: string]: any }, options?: UpdateDeleteOption): Promise<number> {
|
|
200
|
+
let affected = await this.delete(conditions, options);
|
|
201
|
+
if (affected > 1) {
|
|
202
|
+
throw new Error('More then one record has been deleted!');
|
|
203
|
+
}
|
|
204
|
+
return affected;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async deleteAndGet(conditions: { [k: string]: any }, options?: UpdateDeleteOption & Return): Promise<any[]> {
|
|
208
|
+
options = options || {};
|
|
209
|
+
let {sql, parameters} = this.getDeleteQuery(conditions, options);
|
|
210
|
+
sql += " RETURNING " + (options && options.return && Array.isArray(options.return) ? options.return.map(pgUtils.quoteField).join(',') : '*');
|
|
211
|
+
return this.query(sql, parameters);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async deleteAndGetOne(conditions: { [k: string]: any }, options?: UpdateDeleteOption & Return): Promise<any> {
|
|
215
|
+
let result = await this.deleteAndGet(conditions, options);
|
|
216
|
+
if (result.length > 1) {
|
|
217
|
+
throw new Error('More then one record has been deleted!');
|
|
218
|
+
}
|
|
219
|
+
return result[0];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// async deleteAll(options?:UpdateDeleteOption):Promise<number> {
|
|
223
|
+
// let sql = util.format("DELETE FROM %s ", this.qualifiedName);
|
|
224
|
+
// sql = "WITH __RESULT as ( " + sql + " RETURNING 1) SELECT SUM(1) FROM __RESULT";
|
|
225
|
+
// let res = await this.query(sql, {logger:options.logger});
|
|
226
|
+
// return res[0].sum;
|
|
227
|
+
// }
|
|
228
|
+
|
|
229
|
+
async truncate(options?: TruncateOptions): Promise<void> {
|
|
230
|
+
let sql = `TRUNCATE ${this.qualifiedName}`;
|
|
231
|
+
if (options && options.restartIdentity) {
|
|
232
|
+
sql += ' RESTART IDENTITY';
|
|
233
|
+
}
|
|
234
|
+
if (options && options.cascade) {
|
|
235
|
+
sql += ' CASCADE';
|
|
236
|
+
}
|
|
237
|
+
await this.query(sql, null, options);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async find(conditions: { [k: string]: any }, options?: QueryOptions): Promise<T[]>
|
|
241
|
+
async find(conditions: { [k: string]: any }, options?: QueryOptions & Stream): Promise<stream.Readable>
|
|
242
|
+
async find(conditions: { [k: string]: any }, options?: any): Promise<any> {
|
|
243
|
+
options = options || {};
|
|
244
|
+
options.skipUndefined = options.skipUndefined === true || (options.skipUndefined === undefined && ['all', 'select'].indexOf(this.db.config.skipUndefined) > -1);
|
|
245
|
+
let where = _.isEmpty(conditions) ? {
|
|
246
|
+
where: " ",
|
|
247
|
+
params: null
|
|
248
|
+
} : generateWhere(conditions, this.fieldTypes, this.qualifiedName, 0, options.skipUndefined);
|
|
249
|
+
let sql = `SELECT ${pgUtils.processQueryFields(options)} FROM ${this.qualifiedName} ${where.where} ${pgUtils.processQueryOptions(options)}`;
|
|
250
|
+
return options.stream ? this.queryAsStream(sql, where.params, options) : this.query(sql, where.params, options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
async findWhere(where: string, params: any[] | {}, options?: QueryOptions): Promise<T[]>
|
|
255
|
+
async findWhere(where: string, params: any[] | {}, options?: QueryOptions & Stream): Promise<stream.Readable>
|
|
256
|
+
async findWhere(where: string, params: any, options?: any): Promise<any> {
|
|
257
|
+
options = options || {};
|
|
258
|
+
let sql = `SELECT ${pgUtils.processQueryFields(options)} FROM ${this.qualifiedName} WHERE ${where} ${pgUtils.processQueryOptions(options)}`;
|
|
259
|
+
return options.stream ? this.queryAsStream(sql, params, options) : this.query(sql, params, options);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
public async findAll(options?: QueryOptions): Promise<T[]>
|
|
263
|
+
public async findAll(options?: QueryOptions & Stream): Promise<stream.Readable>
|
|
264
|
+
public async findAll(options?: any): Promise<any> {
|
|
265
|
+
options = options || {};
|
|
266
|
+
let sql = `SELECT ${pgUtils.processQueryFields(options)} FROM ${this.qualifiedName} ${pgUtils.processQueryOptions(options)}`;
|
|
267
|
+
return options.stream ? this.queryAsStream(sql, null, options) : this.query(sql, null, options);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async findOne(conditions, options?: QueryOptions): Promise<T> {
|
|
271
|
+
let res = await this.find(conditions, options);
|
|
272
|
+
if (res.length > 1) {
|
|
273
|
+
let logger = (options && options.logger || this.getLogger(false));
|
|
274
|
+
let error = new Error('More then one rows exists');
|
|
275
|
+
pgUtils.logError(logger, { error, sql:this.qualifiedName, params: conditions, connection: this.db.connection });
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
return res[0];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async findFirst(conditions, options?: QueryOptions): Promise<T> {
|
|
282
|
+
options = options || {};
|
|
283
|
+
options.limit = 1;
|
|
284
|
+
let res = await this.find(conditions, options);
|
|
285
|
+
return res[0];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async count(conditions?: {}, options?: CountOption): Promise<number> {
|
|
290
|
+
options = options || {};
|
|
291
|
+
options.skipUndefined = options.skipUndefined === true || (options.skipUndefined === undefined && ['all', 'select'].indexOf(this.db.config.skipUndefined) > -1);
|
|
292
|
+
|
|
293
|
+
let where = _.isEmpty(conditions) ? {
|
|
294
|
+
where: " ",
|
|
295
|
+
params: null
|
|
296
|
+
} : generateWhere(conditions, this.fieldTypes, this.qualifiedName, 0, options.skipUndefined);
|
|
297
|
+
let sql = `SELECT COUNT(*) c FROM ${this.qualifiedName} ${where.where}`;
|
|
298
|
+
return (await this.queryOneField(sql, where.params));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async findOneFieldOnly(conditions, field: string, options?: QueryOptions): Promise<any> {
|
|
302
|
+
options = options || {};
|
|
303
|
+
options.fields = [field];
|
|
304
|
+
let res = await this.findOne(conditions, options);
|
|
305
|
+
return res ? res[field] : null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
private getInsertQuery(records: T[]) {
|
|
310
|
+
let columnsMap = {};
|
|
311
|
+
records.forEach(rec => {
|
|
312
|
+
for (let field in <Object>rec) {
|
|
313
|
+
columnsMap[field] = true;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
let columns = Object.keys(columnsMap);
|
|
317
|
+
let sql = util.format("INSERT INTO %s (%s) VALUES\n", this.qualifiedName, columns.map(pgUtils.quoteField).join(", "));
|
|
318
|
+
let parameters = [];
|
|
319
|
+
let placeholders = [];
|
|
320
|
+
|
|
321
|
+
for (let i = 0, seed = 0; i < records.length; i++) {
|
|
322
|
+
placeholders.push('(' + columns.map(c => "$" + (++seed)).join(', ') + ')');
|
|
323
|
+
parameters.push.apply(parameters, columns.map(c => pgUtils.transformInsertUpdateParams(records[i][c], this.fieldTypes[c])));
|
|
324
|
+
}
|
|
325
|
+
sql += placeholders.join(",\n");
|
|
326
|
+
|
|
327
|
+
return {sql, parameters};
|
|
328
|
+
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
protected getUpdateSetSnipplet(fields: { [k: string]: any }, parameters?:any[] ): { snipplet: string, parameters: any[] } {
|
|
332
|
+
let params = parameters || [];
|
|
333
|
+
let f = [];
|
|
334
|
+
let seed = params.length;
|
|
335
|
+
|
|
336
|
+
_.each(fields, (value, fieldName) => {
|
|
337
|
+
if (value === undefined) return;
|
|
338
|
+
|
|
339
|
+
f.push(util.format('%s = $%s', pgUtils.quoteField(fieldName), (++seed)));
|
|
340
|
+
params.push(pgUtils.transformInsertUpdateParams(value, this.fieldTypes[fieldName]));
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
return {snipplet: f.join(', '), parameters: params};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
protected getUpdateQuery(conditions: { [k: string]: any }, fields: { [k: string]: any }, options?: UpdateDeleteOption): { sql: string, parameters: any[] } {
|
|
347
|
+
options = options || {};
|
|
348
|
+
options.skipUndefined = options.skipUndefined === true || (options.skipUndefined === undefined && this.db.config.skipUndefined === 'all');
|
|
349
|
+
|
|
350
|
+
let hasConditions = true;
|
|
351
|
+
|
|
352
|
+
if (_.isEmpty(fields)) {
|
|
353
|
+
throw new Error('Missing fields for update');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let {snipplet, parameters} = this.getUpdateSetSnipplet(fields);
|
|
357
|
+
let sql = util.format("UPDATE %s SET %s", this.qualifiedName, snipplet);
|
|
358
|
+
|
|
359
|
+
if (!hasConditions || !_.isEmpty(conditions)) {
|
|
360
|
+
let parsedWhere = generateWhere(conditions, this.fieldTypes, this.qualifiedName, parameters.length, options.skipUndefined);
|
|
361
|
+
sql += parsedWhere.where;
|
|
362
|
+
parameters = parameters.concat(parsedWhere.params);
|
|
363
|
+
}
|
|
364
|
+
return {sql, parameters};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
protected getUpsertQuery(record: T, options?: UpsertOption): { sql: string, parameters: any[] } {
|
|
368
|
+
options = options || {};
|
|
369
|
+
|
|
370
|
+
if (_.isEmpty(record)) {
|
|
371
|
+
throw new Error('Missing fields for upsert');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let insert = this.getInsertQuery([record]);
|
|
375
|
+
let {snipplet, parameters} = this.getUpdateSetSnipplet(record, insert.parameters);
|
|
376
|
+
let sql = insert.sql;
|
|
377
|
+
|
|
378
|
+
if (options.columns) {
|
|
379
|
+
sql += " ON CONFLICT (" + options.columns.map(c=>pgUtils.quoteField(c)).join(', ') + ") DO UPDATE SET " + snipplet;
|
|
380
|
+
} else {
|
|
381
|
+
let constraint = options.constraint || this.pkey;
|
|
382
|
+
sql += " ON CONFLICT ON CONSTRAINT " + util.format('"%s"', constraint) + " DO UPDATE SET " + snipplet;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {sql, parameters};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
protected getDeleteQuery(conditions: { [k: string]: any }, options?: UpdateDeleteOption): { sql: string, parameters: any[] } {
|
|
389
|
+
options = options || {};
|
|
390
|
+
options.skipUndefined = options.skipUndefined === true || (options.skipUndefined === undefined && this.db.config.skipUndefined === 'all');
|
|
391
|
+
|
|
392
|
+
let sql = util.format("DELETE FROM %s ", this.qualifiedName);
|
|
393
|
+
|
|
394
|
+
let parsedWhere;
|
|
395
|
+
if (!_.isEmpty(conditions)) {
|
|
396
|
+
parsedWhere = generateWhere(conditions, this.fieldTypes, this.qualifiedName, 0, options.skipUndefined);
|
|
397
|
+
sql += parsedWhere.where;
|
|
398
|
+
}
|
|
399
|
+
return {sql, parameters: parsedWhere && parsedWhere.params || []}
|
|
400
|
+
}
|
|
401
|
+
}
|