-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
142 lines (115 loc) · 4.34 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* @module pg-bind
*/
'use strict';
module.exports = exports = bindQuery;
exports.insert = exports.bindInsert = bindInsertQuery;
/**
* Binds query params from object
*
* @example
* // Result for this command would be:
* // {
* // text: 'INSERT INTO foo (id, name) VALUES ($1, $2)'
* // values: [1, 'kek']
* // }
* let {text, values} = bindQuery('INSERT INTO foo (id, name) VALUES (:id, :user_name)', {id: 1, user_name: 'kek'});
*
* // Somewhere in db-related code...
* pgClient.query(text, values).then(() => ...);
*
* @param {String} queryString Query pattern with values inserted
* @param {Object} replaceObj Object with replacements
* @param {Object} startIndex Index to start with
* @return {Object} Object with final query and it's value bindings
*/
function bindQuery(queryString, replaceObj, startIndex = 1) {
if (!replaceObj) {
throw new Error('You MUST pass replaceObj as second argument');
}
// Initialize all the variables in here
let [binds, values, index] = [new Map, [], startIndex];
// Using String.prototype.replace replace all the params in query pattern
let text = queryString.replace(/[:]?:([a-zA-Z_]+)/g, (search, param) => {
// Return values that begin as typecast
if (search.slice(0, 2) === '::') {
return search;
}
// If we already set this parameter
if (binds.get(param) !== undefined) {
return '$' + binds.get(param);
}
// In other case push parameter into binds and update values array
binds.set(param, index);
values.push(replaceObj[param]);
return '$' + index++;
});
return {text, values};
}
/**
* Binds INSERT query using array of objects
*
* @example
* // Result for this command would be:
* // {
* // text: 'INSERT INTO foo (id, name, age) VALUES ($1, $2, 123), ($3, $4, 123)',
* // values: [1, 'kek', 2, 'lol']
* // }
* let {text, values} = bindInsertQuery('INSERT INTO foo (id, name, age) VALUES (:id, :name, 123)', [
* {id: 1, name: 'kek'},
* {id: 2, name: 'lol'}
* ]);
*
* // Somewhere in db-related code...
* pgClient.query(text, values).then(() => ...);
*
* @param {String} queryString INSERT statement with replacements as in bindQuery
* @param {Object[]} [replacementsArr=[]] Array of objects to use when creating expression
* @return {Object} Object with final query and it's value bindings
*/
function bindInsertQuery(queryString, replacementsArr = []) {
// Whether it's not an array -> do regular bind with single value
if (!Array.isArray(replacementsArr)) {
return bindQuery(queryString, replacementsArr);
}
// Initialize regular expressions to find VALUES (<pattern>)
const start = /VALUES[\s]*\(/img;
const end = /\)[\s]*(?:RETURNING|ON|$)/ig;
// Use this regexp to get lastIndex
const stRes = start.test(queryString);
if (!stRes) {
throw new Error('Cannot find VALUES keyword');
}
// Attach lastIndex to second regexp
end.lastIndex = start.lastIndex;
// Get position of closing brace
let search = end.exec(queryString);
// If search returned null - something went wrong
if (search === null) {
throw new Error('Cannot find closing brace for VALUES expression');
}
search = search && search[0];
// String that contains pattern for VALUES expression
// Can be absolutely anything, supported by bindQuery
// Example: 'string', :value1, DEFAULT, 'string', 123, :value2
const bindPattern = queryString.slice(start.lastIndex, end.lastIndex - search.length);
// Then do the bindings as many times as many replacements
// have been passed into this function.
// I can't call changeable arrays a constant. Don't ask
let index = 1;
let patterns = [];
let values = [];
// Iterate through replacements to bind each one as regular query
for (let replacement of replacementsArr) {
let query = bindQuery(bindPattern, replacement, index);
patterns.push('(' + query.text + ')');
values = values.concat(query.values);
index += query.values.length;
}
const text = [
queryString.slice(0, start.lastIndex - 1),
patterns.join(', '),
queryString.slice(end.lastIndex - search.length + 1)
].join(' ');
return {text, values};
}