UNPKG

8.02 kBJavaScriptView Raw
1/**
2 * @copyright Copyright (c) 2019 Maxim Khorin <maksimovichu@gmail.com>
3 */
4'use strict';
5
6const Base = require('../base/Base');
7
8module.exports = class Query extends Base {
9
10 _index = null;
11 _limit = null;
12 _offset = null;
13 _order = null;
14 _where = null;
15
16 db (db) {
17 this._db = db;
18 return this;
19 }
20
21 from (table) {
22 this._from = table;
23 return this;
24 }
25
26 index (column) {
27 this._index = column;
28 return this;
29 }
30
31 raw () {
32 return this;
33 }
34
35 getDb () {
36 return this._db;
37 }
38
39 getTable () {
40 return this._from;
41 }
42
43 getIndex () {
44 return this._index;
45 }
46
47 getRaw () {
48 return this._raw;
49 }
50
51 // OPTIONS
52
53 getOptions () {
54 return this._options;
55 }
56
57 options (data) {
58 this._options = data;
59 return this;
60 }
61
62 addOptions (data) {
63 this._options = this._options ? Object.assign(this._options, data) : data;
64 return this;
65 }
66
67 // SELECT
68
69 select (data) {
70 if (Array.isArray(data)) {
71 this.select(data[0]);
72 for (let i = 1; i < data.length; ++i) {
73 this.addSelect(data[i]);
74 }
75 } else if (typeof data === 'string') {
76 this._select = {[data]: 1};
77 } else {
78 this._select = data;
79 }
80 return this;
81 }
82
83 addSelect (data) {
84 if (!this._select) {
85 return this.select(data);
86 }
87 if (Array.isArray(data)) {
88 for (const item of data) {
89 this.addSelect(item);
90 }
91 } else if (typeof data === 'string') {
92 this._select[data] = 1;
93 } else {
94 Object.assign(this._select, data);
95 }
96 return this;
97 }
98
99 getSelect () {
100 return this._select;
101 }
102
103 // WHERE
104
105 where (condition) {
106 this._where = condition;
107 return this;
108 }
109
110 addWhere (operator, ...conditions) {
111 if (!this._where) {
112 this._where = conditions.length > 1 ? [operator, ...conditions] : conditions[0];
113 } else if (this._where[0] === operator) {
114 this._where.push(...conditions);
115 } else {
116 this._where = [operator, this._where, ...conditions];
117 }
118 }
119
120 getWhere () {
121 return this._where;
122 }
123
124 and () {
125 if (arguments[0]) {
126 this.addWhere('AND', ...arguments);
127 }
128 return this;
129 }
130
131 or () {
132 if (arguments[0]) {
133 this.addWhere('OR', ...arguments);
134 }
135 return this;
136 }
137
138 filter (condition) {
139 return this.where(this.filterCondition(condition));
140 }
141
142 andFilter (condition) {
143 return this.and(this.filterCondition(condition));
144 }
145
146 orFilter (condition) {
147 return this.or(this.filterCondition(condition));
148 }
149
150 andNotIn (key, value) {
151 return this.and(['NOT IN', key, value]);
152 }
153
154 // ORDER
155 /**
156 * @param data - { attr1: 1, attr2: -1 }
157 */
158 order (data) {
159 this._order = data;
160 return this;
161 }
162
163 addOrder (data) {
164 this._order = this._order ? Object.assign(this._order, data) : data;
165 return this;
166 }
167
168 orderByKeys (keys) {
169 this._orderByKeys = keys;
170 return this;
171 }
172
173 // LIMIT
174
175 limit (limit) {
176 this._limit = limit === null ? null : parseInt(limit);
177 return this;
178 }
179
180 getLimit () {
181 return this._limit;
182 }
183
184 // OFFSET
185
186 offset (value) {
187 this._offset = value === null ? null : parseInt(value);
188 return this;
189 }
190
191 getOffset () {
192 return this._offset;
193 }
194
195 // COMMAND
196
197 all () {
198 return this._db.queryAll(this);
199 }
200
201 one () {
202 return this._db.queryOne(this);
203 }
204
205 column (key) {
206 return this._db.queryColumn(this, key);
207 }
208
209 distinct (key) {
210 return this._db.queryDistinct(this, key);
211 }
212
213 id () {
214 return this._db.queryScalar(this, '_id');
215 }
216
217 ids () {
218 return this._db.queryColumn(this, '_id');
219 }
220
221 scalar (key) {
222 return this._db.queryScalar(this, key);
223 }
224
225 insert (data) {
226 return this._db.queryInsert(this, data);
227 }
228
229 update (data) {
230 return this._db.queryUpdate(this, data);
231 }
232
233 updateAll (data) {
234 return this._db.queryUpdateAll(this, data);
235 }
236
237 upsert (data) {
238 return this._db.queryUpsert(this, data);
239 }
240
241 delete () {
242 return this._db.queryDelete(this);
243 }
244
245 count () {
246 return this._db.queryCount(this);
247 }
248
249 max (key) {
250 this._order = {[key]: -1};
251 this._limit = 1;
252 return this.scalar(key);
253 }
254
255 min (key) {
256 this._order = {[key]: 1};
257 this._limit = 1;
258 return this.scalar(key);
259 }
260
261 //
262
263 isEmptyValue (value) {
264 return value === undefined || value === null || value === ''
265 || (typeof value === 'string' && value.trim() === '')
266 || (typeof value === 'object' && !Object.values(value).length);
267 }
268
269 prepare () {
270 }
271
272 afterBuild () {
273 // restore _where after filter by via relation models
274 if (this._whereBeforeFilter !== undefined) {
275 this._where = this._whereBeforeFilter;
276 delete this._whereBeforeFilter;
277 }
278 }
279
280 populate (docs) {
281 return this._index
282 ? QueryHelper.indexObjects(docs, this._index)
283 : docs;
284 }
285
286 filterCondition (data) {
287 return Array.isArray(data)
288 ? this.filterOperatorCondition(data)
289 : data ? this.filterHashCondition(data)
290 : null;
291 }
292
293 // operator format: [operator, operand 1, operand 2, ...]
294 filterOperatorCondition (data) {
295 switch (data[0]) {
296 case 'NOT':
297 case 'AND':
298 case 'OR':
299 return this.filterSerialCondition();
300 case 'BETWEEN':
301 case 'NOT BETWEEN':
302 return !this.isEmptyValue(data[1]) && !this.isEmptyValue(data[2]) ? data : null;
303 }
304 return this.isEmptyValue(data[1]) ? null : data;
305 }
306
307 filterSerialCondition (data) { // OR AND NOT
308 for (let i = data.length - 1; i > 0; --i) {
309 const item = this.filterCondition(data[i]);
310 if (this.isEmptyValue(item)) {
311 data.splice(i, 1);
312 } else {
313 data[i] = item;
314 }
315 }
316 return data.length > 1 ? data : null;
317 }
318
319 // hash format: { column1: value1, column2: value2, ... }
320 filterHashCondition (data) {
321 const result = {};
322 for (const key of Object.keys(data)) {
323 if (!this.isEmptyValue(data[key])) {
324 result[key] = data[key];
325 }
326 }
327 return Object.values(result).length ? result : null;
328 }
329
330 // sort an array of objects with a key attribute by an array of keys
331 sortOrderByKeys (docs) {
332 const keys = this._orderByKeys;
333 if (!Array.isArray(keys) || keys.length < 2) {
334 return docs;
335 }
336 // documents can be with equal key values
337 const data = IndexHelper.indexObjectArrays(docs, this.refKey);
338 const result = [];
339 for (const key of keys) {
340 if (Array.isArray(data[key])) {
341 result.push(...data[key]);
342 delete data[key]; // ignore same keys
343 }
344 }
345 return result;
346 }
347};
348
349const IndexHelper = require('../helper/IndexHelper');
350const QueryHelper = require('../helper/QueryHelper');
\No newline at end of file