1 | var async = require('./async')
|
2 | var hide = require('./hideProperty')
|
3 | var Row = require('./row')
|
4 | var extend = require('./extend')
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | var Table = module.exports = function Table(opts, cb) {
|
12 | var self = this
|
13 | if (!(self instanceof Table)) {
|
14 | return new Table(opts, cb)
|
15 | }
|
16 |
|
17 | var tableName = opts.tableName
|
18 |
|
19 | self.name = tableName
|
20 | self.primaryKey = []
|
21 | self.columns = []
|
22 | self.fk = {}
|
23 | self.db = opts.db
|
24 |
|
25 | hide(self, 'db')
|
26 |
|
27 | var models = self.db._opts.models
|
28 | if (models && models[tableName] && typeof models[tableName] === 'function') {
|
29 | var model = models[tableName]
|
30 | self.Row = model
|
31 | }
|
32 |
|
33 | if (opts.fromCache) {
|
34 | extend(self, self.db._opts.schema[tableName])
|
35 | process.nextTick(cb)
|
36 | return
|
37 | }
|
38 |
|
39 | async.each([
|
40 | self.getColumns.bind(self),
|
41 | self.getForeignKeys.bind(self),
|
42 | self.getPrimaryKeyDefinition.bind(self)
|
43 | ], function (fn, done) {
|
44 | fn(done)
|
45 | }, cb)
|
46 | }
|
47 |
|
48 |
|
49 | Table.prototype.insert = function(data, cb) {
|
50 | return this.save(data, cb)
|
51 | }
|
52 |
|
53 |
|
54 | Table.prototype.count = function(opts, cb) {
|
55 | var options = { count: true }
|
56 | extend(options, opts)
|
57 | return this.find(options, cb)
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | Table.prototype.find = function(opts, cb) {
|
73 | var self = this
|
74 | var iq = self.db._platform.identifierQuoteChar
|
75 | opts = opts || {}
|
76 | if (typeof opts === 'function') {
|
77 | cb = opts
|
78 | opts = {}
|
79 | }
|
80 |
|
81 | cb = cb || self.db._promiseResolver()
|
82 |
|
83 | var select = iq + self.primaryKey.join(iq + ', ' + iq) + iq
|
84 | if (opts.count) {
|
85 | select = 'count(*) as count'
|
86 | }
|
87 | var sql = [
|
88 | 'SELECT ' + select,
|
89 | 'FROM ' + iq + self.name + iq,
|
90 | 'WHERE true'
|
91 | ]
|
92 | var values = opts.params || {}
|
93 |
|
94 | if (opts) {
|
95 |
|
96 |
|
97 | if (isArray(opts.where)) {
|
98 | opts.where.forEach(function(val, i) {
|
99 | if (isString(val)) {
|
100 | sql.push('AND ' + val)
|
101 | }
|
102 | });
|
103 | } else if (isString(opts.where)) {
|
104 | sql.push('AND ' + opts.where)
|
105 | } else if (opts.where) {
|
106 | Object.keys(opts.where).forEach(function(property) {
|
107 | if (typeof opts.where[property] === 'undefined') {
|
108 | return
|
109 | }
|
110 | sql.push('AND ' + iq + property + iq + " = :" + property)
|
111 | values[property] = opts.where[property]
|
112 | })
|
113 | }
|
114 |
|
115 | if (!opts.count) {
|
116 |
|
117 | if (opts.order) {
|
118 | if (isArray(opts.order)) {
|
119 | opts.order = opts.order.join(', ')
|
120 | }
|
121 | if (opts.order) {
|
122 | sql.push('ORDER BY ' + opts.order)
|
123 | }
|
124 | }
|
125 |
|
126 | if (opts.limit) {
|
127 | sql.push('LIMIT ' + opts.limit)
|
128 | }
|
129 |
|
130 | if (isInteger(opts.offset)) {
|
131 | sql.push('OFFSET ' + opts.offset)
|
132 | }
|
133 | }
|
134 |
|
135 | }
|
136 | sql = sql.join('\n')
|
137 | self.db.execute(sql, values, function(err, rs) {
|
138 | if (err) return cb(err)
|
139 | if (opts.count) {
|
140 | return cb(null, parseInt(rs[0].count, 10))
|
141 | }
|
142 | self.mget(rs, function(err, rows) {
|
143 | if (err) return cb(err)
|
144 | if (!opts.hydrate) {
|
145 | return cb(null, rows)
|
146 | }
|
147 |
|
148 | async.each(rows, function (row, done) {
|
149 | row.hydrate(opts.hydrate, done)
|
150 | }, function (err) {
|
151 | return cb(err, rows)
|
152 | })
|
153 | })
|
154 | })
|
155 |
|
156 | return cb.promise ? cb.promise : this
|
157 | }
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | Table.prototype.findOne = function(params, cb) {
|
165 | params.limit = 1;
|
166 | cb = cb || this.db._promiseResolver()
|
167 | this.find(params, function(err, rs) {
|
168 | if (err) return cb(err)
|
169 | cb(null, rs[0])
|
170 | })
|
171 | return cb.promise ? cb.promise : this
|
172 | };
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | Table.prototype.getColumns = function(cb) {
|
179 | cb = cb || function(){}
|
180 | var self = this
|
181 | self.db._platform.getColumns(self.name, function(err, columns) {
|
182 | if (err) return cb(err)
|
183 | self.columns = columns
|
184 | cb(null)
|
185 | })
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | Table.prototype.getPrimaryKeyDefinition = function(cb) {
|
193 | var self = this
|
194 | self.db._platform.getPrimaryKeyDefinition(self.name, function(err, pk) {
|
195 | if (err) return cb(err)
|
196 | self.primaryKey = pk
|
197 | cb(null)
|
198 | })
|
199 | }
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | Table.prototype.getForeignKeys = function(cb) {
|
206 | var self = this
|
207 | cb = cb || function(){}
|
208 | self.db._platform.getForeignKeys(self.name, function(err, foreignKeys) {
|
209 | if (err) return cb(err)
|
210 | self.fk = foreignKeys
|
211 | cb(null)
|
212 | })
|
213 | }
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 | Table.prototype.get = function(key, params, cb) {
|
229 | params = params || {}
|
230 | if (typeof params === 'function') {
|
231 | cb = params
|
232 | params = {}
|
233 | }
|
234 | var self = table = this
|
235 | cb = cb || self.db._promiseResolver()
|
236 | var pkWhere = self.getPrimaryKeyWhereClause(key)
|
237 | var iq = self.db._platform.identifierQuoteChar
|
238 | var sql = [
|
239 | 'SELECT *',
|
240 | 'FROM ' + iq + self.name + iq,
|
241 | 'WHERE ' + pkWhere
|
242 | ]
|
243 | var memoMs = self.db._opts.memoize
|
244 | var cacheKey = self.getCacheKey(key)
|
245 | var memo = self.db._memo
|
246 |
|
247 |
|
248 | if (memoMs) {
|
249 | if (memo[cacheKey] && memo[cacheKey].timestamp + memoMs > Date.now()) {
|
250 |
|
251 | var interval = setInterval(function() {
|
252 | if (!memo[cacheKey].queryInProgress) {
|
253 | clearInterval(interval)
|
254 | return instantiate(memo[cacheKey].data)
|
255 | }
|
256 | }, 1)
|
257 | return cb.promise ? cb.promise : self
|
258 | }
|
259 | memo[cacheKey] = {
|
260 | queryInProgress: true
|
261 | }
|
262 | }
|
263 |
|
264 | var noCache = !self.db._opts.cache || typeof self.db._opts.cache.get !== 'function'
|
265 | if (noCache) {
|
266 | getFromDb()
|
267 | return cb.promise ? cb.promise : self
|
268 | }
|
269 |
|
270 |
|
271 | var start = Date.now()
|
272 | self.db._opts.cache.get(cacheKey, function(err, json) {
|
273 | if (err) return cb(err)
|
274 | if (json) {
|
275 | var val
|
276 | try {
|
277 | val = JSON.parse(json)
|
278 | } catch (e) {}
|
279 |
|
280 | if (val) {
|
281 | if (self.db._opts.debug) {
|
282 | self.db._opts.debug(
|
283 | '\n' + 'cache.get(' + cacheKey + ')\n' +
|
284 | (Date.now() - start) + 'ms'
|
285 | )
|
286 | }
|
287 | return instantiate(val)
|
288 | }
|
289 | }
|
290 | getFromDb()
|
291 | })
|
292 | return cb.promise ? cb.promise : self
|
293 |
|
294 | function instantiate(val) {
|
295 | var row = self.instantiateRow(val)
|
296 | if (!params.hydrate) {
|
297 | return cb(null, row)
|
298 | }
|
299 | row.hydrate(params.hydrate, function (err) {
|
300 | if (err) return cb(err)
|
301 | return cb(null, row)
|
302 | })
|
303 | }
|
304 |
|
305 | function getFromDb() {
|
306 | if (params.cacheOnly) {
|
307 | return cb(null, null)
|
308 | }
|
309 | self.db.execute(sql, function(err, rs) {
|
310 | if (err) return cb(err)
|
311 | var r = rs[0]
|
312 |
|
313 | if (memoMs) {
|
314 | memo[cacheKey] = {
|
315 | timestamp: Date.now(),
|
316 | data: r
|
317 | }
|
318 | }
|
319 | if (!r) {
|
320 | var error = new Error('Not found: ' + self.name + ':' + key)
|
321 | error.notFound = true
|
322 | error.table = self.name
|
323 | error.key = key
|
324 | return cb(error)
|
325 | }
|
326 |
|
327 | if (self.db._opts.cache && typeof self.db._opts.cache.set === 'function') {
|
328 | self.db._opts.cache.set(cacheKey, JSON.stringify(r), function(err) {
|
329 | if (err) console.error(err)
|
330 | })
|
331 | }
|
332 | instantiate(r)
|
333 | })
|
334 | }
|
335 | }
|
336 |
|
337 |
|
338 | Table.prototype.instantiateRow = function(data) {
|
339 | var row = Row(data, this)
|
340 | row._data = data
|
341 | return row
|
342 | }
|
343 |
|
344 |
|
345 | Table.prototype.mget = function(keys, cb) {
|
346 | var self = this
|
347 | var rows = []
|
348 | var cacheKeys = []
|
349 | var iq = self.db._platform.identifierQuoteChar
|
350 | cb = cb || self.db._promiseResolver()
|
351 | async.forEachOf(keys, function(key, i, done) {
|
352 | cacheKeys[i] = self.getCacheKey(key)
|
353 | self.get(key, { cacheOnly: true }, function(err, obj) {
|
354 | rows[i] = obj
|
355 | done(err)
|
356 | })
|
357 | }, function(err) {
|
358 | if (err) return cb(err)
|
359 |
|
360 | var haveAllRows = true
|
361 | var where = []
|
362 | rows.forEach(function (row, i) {
|
363 | if (row) return
|
364 | haveAllRows = false
|
365 | var pkWhere = self.getPrimaryKeyWhereClause(keys[i])
|
366 | where.push('(' + pkWhere + ')')
|
367 | })
|
368 | if (haveAllRows) {
|
369 | return cb(null, rows)
|
370 | }
|
371 | var sql = [
|
372 | 'SELECT *',
|
373 | 'FROM ' + iq + self.name + iq,
|
374 | 'WHERE (' + where.join(' OR ') + ')'
|
375 | ]
|
376 | self.db.execute(sql, function(err, rs) {
|
377 | if (err) return cb(err)
|
378 | rs.forEach(function (row) {
|
379 | var cacheKey = self.getCacheKeyFromRow(row)
|
380 | var i = cacheKeys.indexOf(cacheKey)
|
381 | rows[i] = self.instantiateRow(row)
|
382 |
|
383 | if (self.db._opts.cache && typeof self.db._opts.cache.set === 'function') {
|
384 | self.db._opts.cache.set(cacheKey, JSON.stringify(row), function(err) {
|
385 | if (err) console.error(err)
|
386 | })
|
387 | }
|
388 | })
|
389 | cb(null, rows)
|
390 | })
|
391 | })
|
392 | return cb.promise ? cb.promise : this
|
393 | }
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 | Table.prototype.getPrimaryKeyWhereClause = function(key) {
|
400 | var self = this
|
401 | var iq = self.db._platform.identifierQuoteChar
|
402 | var criteria = ''
|
403 | if (!isArray(key) && !isObject(key)) {
|
404 | var temp = key
|
405 | key = []
|
406 | key[0] = temp
|
407 | }
|
408 | self.primaryKey.forEach(function(field, i) {
|
409 | var val = key[field]
|
410 | if (isArray(key)) {
|
411 | val = key[i]
|
412 | }
|
413 | if (criteria.length > 0) {
|
414 | criteria += ' AND '
|
415 | }
|
416 | if (!val && val !== 0) {
|
417 | throw new Error('Invalid value specified for "' + self.name + '" primary key.')
|
418 | }
|
419 | criteria += iq + self.name + iq + '.' + iq + field + iq + " = " + self.db._query.queryValues(':val', {val: val})
|
420 | })
|
421 | return criteria;
|
422 | }
|
423 |
|
424 |
|
425 | Table.prototype.getCacheKey = function(key) {
|
426 | var self = this
|
427 | var cacheKey = ''
|
428 | if (isObject(key)) {
|
429 | Object.keys(key).forEach(function(prop) {
|
430 | if (cacheKey) {
|
431 | cacheKey = cacheKey + ','
|
432 | }
|
433 | cacheKey = cacheKey + key[prop]
|
434 | })
|
435 | } else if (isArray(key)) {
|
436 | key.forEach(function(val) {
|
437 | if (cacheKey) {
|
438 | cacheKey = cacheKey + ','
|
439 | }
|
440 | cacheKey = cacheKey + val
|
441 | })
|
442 | } else {
|
443 | cacheKey = key
|
444 | }
|
445 | cacheKey = self.name + ':' + cacheKey
|
446 | return cacheKey
|
447 | }
|
448 |
|
449 |
|
450 | Table.prototype.getCacheKeyFromRow = function(row) {
|
451 | var self = this
|
452 | var pk = {}
|
453 | self.primaryKey.forEach(function (column) {
|
454 | pk[column] = row[column]
|
455 | })
|
456 | return self.getCacheKey(pk)
|
457 | }
|
458 |
|
459 |
|
460 | Table.prototype.invalidateMemo = function(key) {
|
461 | var self = this
|
462 | var cacheKey = self.getCacheKey(key)
|
463 | delete self.db._memo[cacheKey]
|
464 | }
|
465 |
|
466 |
|
467 | Table.prototype.save = function(data, params, cb) {
|
468 | params = params || {}
|
469 | if (typeof params === 'function') {
|
470 | cb = params
|
471 | params = {}
|
472 | }
|
473 | var row = new Row(data, this)
|
474 | return row.save(params, cb)
|
475 | }
|
476 |
|
477 |
|
478 |
|
479 | function isArray(obj) {
|
480 | return Object.prototype.toString.call(obj) === '[object Array]'
|
481 | }
|
482 |
|
483 | function isString(obj) {
|
484 | return toString.call(obj) === '[object String]'
|
485 | }
|
486 |
|
487 | function isObject(obj) {
|
488 | return toString.call(obj) === '[object Object]'
|
489 | }
|
490 |
|
491 |
|
492 |
|
493 | var isInteger = Number.isInteger || function(val) {
|
494 | return typeof val === "number" &&
|
495 | ! Number.isNaN(val) &&
|
496 | Number.isFinite(val) &&
|
497 | val > -9007199254740992 &&
|
498 | val < 9007199254740992 &&
|
499 | parseInt(val, 10) === val;
|
500 | }
|