UNPKG

18.1 kBtext/coffeescriptView Raw
1# Copyright (c) 2013 Rod Vagg, MIT License
2# Copyright (c) 2014 Riceball LEE, MIT License
3xtend = require("xtend")
4AbstractObject = require("abstract-object")
5Codec = require("buffer-codec")
6utf8ByteLength = Codec.getByteLen
7Errors = require("./abstract-error")
8try AbstractIterator = require("abstract-iterator")
9AbstractChainedBatch = require("./abstract-chained-batch")
10setImmediate = global.setImmediate or process.nextTick
11
12AbstractError = Errors.AbstractError
13NotImplementedError = Errors.NotImplementedError
14InvalidArgumentError = Errors.InvalidArgumentError
15OpenError = Errors.OpenError
16CloseError = Errors.CloseError
17inherits = require("abstract-object/lib/util/inherits")
18isString = require("abstract-object/lib/util/isString")
19isFunction = require("abstract-object/lib/util/isFunction")
20isArray = require("abstract-object/lib/util/isArray")
21
22module.exports = class AbstractNoSQL
23 inherits AbstractNoSQL, AbstractObject
24
25 constructor: ->
26 super
27 initialize:(location) ->
28 #not all database have the location argument.
29 throw new InvalidArgumentError("constructor requires a location string argument") if location and typeof location isnt "string"
30 @location = location
31 finalize: ->
32 if @_opened
33 if @_closeSync then @closeSync()
34 else @closeAsync()
35 @_options = null
36
37 @::__defineGetter__ "opened", ->
38 !!@_opened
39
40 setOpened: (aValue, options)->
41 if aValue
42 @_opened = true
43 @_options = options if options
44 @emit "ready"
45 @emit "open"
46 else
47 @_opened = false
48 @emit "closed"
49
50 #the optimal low-level sync functions:
51 isExistsSync: (key, options) ->
52 options = {} unless options?
53 key = String(key) unless @_isBuffer(key)
54 if @_isExistsSync
55 result = @_isExistsSync(key, options)
56 return result
57 else if @_getSync
58 try
59 @_getSync key, options
60 return true
61 catch err
62 if AbstractError.isNotFound(err)
63 return false
64 else
65 throw err
66 throw new NotImplementedError()
67 isExistSync: @::isExistsSync
68
69 getSync: (key, options) ->
70 if @_getSync
71 options = {} unless options?
72 throw err if err = @_checkKey(key, "key")
73 key = String(key) unless @_isBuffer(key) #TODO should move to low-level db.
74 result = @_getSync(key, options)
75 return result
76 throw new NotImplementedError()
77
78 getBufferSync: (key, destBuffer, options) ->
79 if @_getBufferSync
80 options = {} unless options?
81 options.offset = 0 unless options.offset?
82 result = @_getBufferSync(key, destBuffer, options)
83 return result
84 throw new NotImplementedError()
85
86 mGetSync: (keys, options) ->
87 if @_mGetSync
88 options = {} unless options?
89 options.raiseError = options.raiseError isnt false
90 needKeyName = options.keys
91 arr = @_mGetSync(keys, options)
92 i = 0
93 result = []
94 while i < arr.length
95 if needKeyName isnt false
96 result.push
97 key: arr[i]
98 value: arr[++i]
99 else
100 result.push arr[i]
101 i++
102 return result
103 throw new NotImplementedError()
104
105 putSync: (key, value, options) ->
106 if @_putSync
107 options = {} unless options?
108 result = @_putSync(key, value, options)
109 return result
110 throw new NotImplementedError()
111
112 delSync: (key, options) ->
113 if @_delSync
114 options = {} unless options?
115 result = @_delSync(key, options)
116 return result
117 throw new NotImplementedError()
118
119 batchSync: (operations, options) ->
120 if @_batchSync
121 options = {} unless options?
122 unless isArray(operations)
123 throw new InvalidArgumentError("batch(operations) requires an array argument")
124 for e in operations
125 continue unless typeof e is "object"
126 throw err if err = @_checkKey(e.type, "type")
127 throw err if err = @_checkKey(e.key, "key")
128 result = @_batchSync(operations, options)
129 return result
130 throw new NotImplementedError()
131
132 approximateSizeSync: (start, end) ->
133 if @_approximateSizeSync
134 if not start? or not end?
135 throw new InvalidArgumentError("approximateSize() requires valid `start`, `end` arguments")
136 start = String(start) unless @_isBuffer(start)
137 end = String(end) unless @_isBuffer(end)
138 result = @_approximateSizeSync(start, end)
139 return result
140 throw new NotImplementedError()
141
142 openSync: (options) ->
143 if @_openSync
144 options = @_options || {} unless options?
145 options.createIfMissing = options.createIfMissing isnt false
146 options.errorIfExists = !!options.errorIfExists
147 @emit "opening", options
148 result = @_openSync(options)
149 @setOpened true, options if result
150 result = @ if result
151 return result
152 throw new NotImplementedError()
153
154
155 #if successful should return true.
156 closeSync: ->
157 if @_closeSync
158 @emit "closing"
159 result = @_closeSync()
160 @setOpened false if result
161 return result
162 throw new NotImplementedError()
163
164
165 #the async methods simulated by sync methods:
166 #the derived class can override these methods to implement the real async methods for better performance.
167 _open: (options, callback) ->
168 that = this
169 if @_openSync
170 setImmediate ->
171 result = undefined
172 try
173 result = that._openSync(options)
174 catch err
175 callback err
176 return
177 if result
178 callback null, result
179 else
180 callback new OpenError("can not open database.")
181
182 else
183 setImmediate callback
184
185 _close: (callback) ->
186 that = this
187 if @_closeSync
188 setImmediate ->
189 result = undefined
190 try
191 result = that._closeSync()
192 catch err
193 callback err
194 return
195 if result
196 callback null, result
197 else
198 callback new CloseError("can not close database.")
199
200 else
201 setImmediate callback
202
203 _isExists: (key, options, callback) ->
204 that = this
205 if @_isExistsSync
206 setImmediate ->
207 result = undefined
208 try
209 result = that._isExistsSync(key, options)
210 catch err
211 callback err
212 return
213 callback null, result
214
215 else
216 @_get key, options, (err, value) ->
217 if err
218 if AbstractError.isNotFound(err)
219 callback null, false
220 else
221 callback err
222 else
223 callback null, true
224
225 _getBuffer: (key, destBuffer, options, callback) ->
226 that = this
227 if @_getSync or @_getBufferSync isnt AbstractNoSQL::_getBufferSync
228 setImmediate ->
229 result = undefined
230 try
231 result = that._getBufferSync(key, destBuffer, options)
232 catch err
233 callback err
234 return
235 callback null, result
236 else if @_get
237 @_get key, options, (err, value)->
238 return callback(err) if err
239 result = utf8ByteLength(value)
240 if destBuffer
241 result = Math.min(result, destBuffer.length)
242 result = destBuffer.write(value, options.offset, result) if result
243 callback null, result
244 else
245 setImmediate callback
246
247 _getBufferSync: (key, destBuffer, options) ->
248 if @_getSync
249 value = @_getSync(key, options)
250 result = utf8ByteLength(value)
251 if destBuffer
252 result = Math.min(result, destBuffer.length)
253 result = destBuffer.write(value, options.offset, result) if result
254 return result
255 else
256 throw new NotImplementedError('_mGetSync: _getSync is not implemented.')
257
258 _mGetSync: (keys, options) ->
259 if @_getSync
260 result = []
261 needKeyName = options.keys
262 raiseError = options.raiseError
263 options.asBuffer = options.asBuffer is true
264 for key in keys
265 try
266 value = @_getSync(key, options)
267 catch err
268 throw err if raiseError
269 value = undefined
270 if needKeyName isnt false
271 result.push key, value
272 else
273 result.push value
274 return result
275 else
276 throw new NotImplementedError('_mGetSync: _getSync is not implemented.')
277
278 _mGet: (keys, options, callback) ->
279 that = this
280 if @_getSync or @_mGetSync isnt AbstractNoSQL::_mGetSync
281 setImmediate ->
282 result = undefined
283 try
284 result = that._mGetSync keys, options
285 catch err
286 callback err
287 return
288 callback null, result
289 else if keys.length > 0 and @_get
290 result = []
291 i = 0
292 needKeyName = options.keys
293 raiseError = options.raiseError
294
295 readNext = (err, value)->
296 return callback(err) if err and raiseError
297
298 if needKeyName isnt false
299 result.push keys[i], value
300 else
301 result.push value
302 i++
303 return callback(null, result) if i >= keys.length
304 that._get keys[i], options, readNext
305 @_get keys[i], options, readNext
306 else
307 setImmediate callback
308
309 _get: (key, options, callback) ->
310 that = this
311 if @_getSync
312 setImmediate ->
313 result = undefined
314 try
315 result = that._getSync(key, options)
316 catch err
317 callback err
318 return
319 callback null, result
320
321 else
322 setImmediate callback
323
324 _put: (key, value, options, callback) ->
325 that = this
326 if @_putSync
327 setImmediate ->
328 result = undefined
329 try
330 result = that._putSync(key, value, options)
331 catch err
332 callback err
333 return
334 callback null, result
335
336 else
337 setImmediate callback
338
339 _del: (key, options, callback) ->
340 that = this
341 if @_delSync
342 setImmediate ->
343 result = undefined
344 try
345 result = that._delSync(key, options)
346 catch err
347 callback err
348 return
349 callback null, result
350
351 else
352 setImmediate callback
353
354 _batch: (array, options, callback) ->
355 that = this
356 if @_batchSync
357 setImmediate ->
358 result = undefined
359 try
360 result = that._batchSync(array, options)
361 catch err
362 callback err
363 return
364 callback null, result
365
366 else
367 setImmediate callback
368
369
370 #TODO: remove from here, not a necessary primitive
371 _approximateSize: (start, end, callback) ->
372 that = this
373 if @_approximateSizeSync
374 setImmediate ->
375 result = undefined
376 try
377 result = that._approximateSizeSync(start, end)
378 catch err
379 callback err
380 return
381 callback null, result
382 else
383 setImmediate callback
384
385
386 #slower impl:
387 #
388 #_exec: (fn, args, callback) ->
389 # that = this
390 # if fn then setImmediate ->
391 # result
392 # try
393 # result = fn.apply(that, args)
394 # catch (err)
395 # callback(err)
396 # return
397 #
398 # callback(null, result)
399 # else
400 # setImmediate(callback)
401 #
402 #_open: (options, callback) ->
403 # this._exec(this._openSync, [options], callback)
404 #
405 #
406 openAsync: (options, callback) ->
407 options = {} unless options?
408 options.createIfMissing = options.createIfMissing isnt false
409 options.errorIfExists = !!options.errorIfExists
410 that = this
411 @emit "opening", options
412 @_open options, (err, result) ->
413 that.setOpened true, options if not err?
414 callback err, result
415 open: (options, callback) ->
416 if isFunction options
417 callback = options
418 options = undefined
419 if callback
420 @openAsync options, callback
421 else
422 @openSync options
423
424 closeAsync: (callback) ->
425 that = this
426 callback = undefined unless isFunction callback
427 @emit "closing"
428 @_close (err, result) ->
429 return that.dispatchError err, callback if err
430 that.setOpened false
431 callback null, result if callback
432 close: (callback) ->
433 if callback
434 @closeAsync callback
435 else
436 @closeSync()
437
438 isExistsAsync: (key, options, callback) ->
439 options = {} unless options?
440 key = String(key) unless @_isBuffer(key)
441 @_isExists key, options, callback
442 isExists: (key, options, callback) ->
443 if isFunction options
444 callback = options
445 options = {}
446 else
447 if callback
448 @isExistsAsync key, options, callback
449 else
450 @isExistsSync key, options
451 isExist: @::isExists
452
453 getBufferAsync: (key, destBuffer, options, callback) ->
454 options = {} unless options?
455 options.offset = 0 unless options.offset?
456 @_getBuffer key, destBuffer, options, callback
457 getBuffer: (key, destBuffer, options, callback) ->
458 err = undefined
459 if isFunction options
460 callback = options
461 options = {}
462 if callback
463 @getBufferAsync key, destBuffer, options, callback
464 else
465 @getBufferSync key, destBuffer, options
466
467 mGetAsync: (keys, options, callback) ->
468 options = {} unless options?
469 options.asBuffer = options.asBuffer is true
470 options.raiseError = options.raiseError isnt false
471 needKeyName = options.keys isnt false
472 @_mGet keys, options, (err, arr)->
473 return callback(err) if err
474 if needKeyName
475 i = 0
476 result = []
477 while i < arr.length
478 result.push
479 key: arr[i]
480 value: arr[++i]
481 i++
482 else
483 result = arr
484 callback null, result
485 mGet: (keys, options, callback) ->
486 err = undefined
487 if isFunction options
488 callback = options
489 options = {}
490 else
491 if callback
492 @mGetAsync keys, options, callback
493 else
494 @mGetSync keys, options
495
496 getAsync: (key, options, callback) ->
497 options = {} unless options?
498 return callback(err) if err = @_checkKey(key, "key")
499 key = String(key) unless @_isBuffer(key)
500 options.asBuffer = options.asBuffer is true
501 @_get key, options, callback
502 get: (key, options, callback) ->
503 err = undefined
504 if isFunction options
505 callback = options
506 options = {}
507 if callback
508 @getAsync key, options, callback
509 else
510 @getSync key, options
511
512 putAsync: (key, value, options, callback) ->
513 options = {} unless options?
514 return callback(err) if err = @_checkKey(key, "key", @_isBuffer)
515 key = String(key) unless @_isBuffer(key)
516 # coerce value to string in node, don't touch it in browser
517 # (indexeddb can store any JS type)
518 value = String(value) if value? and not @_isBuffer(value) and not process.browser
519 @_put key, value, options, callback
520
521 put: (key, value, options, callback) ->
522 err = undefined
523 if isFunction options
524 callback = options
525 options = {}
526 if callback
527 @putAsync key, value, options, callback
528 else
529 @putSync key, value, options
530
531 delAsync: (key, options, callback) ->
532 options = {} unless options?
533 return callback(err) if err = @_checkKey(key, "key", @_isBuffer)
534 key = String(key) unless @_isBuffer(key)
535 @_del key, options, callback
536 del: (key, options, callback) ->
537 err = undefined
538 if isFunction options
539 callback = options
540 options = {}
541 if callback
542 @delAsync key, options, callback
543 else
544 @delSync key, options
545
546 batchAsync: (array, options, callback) ->
547 options = {} unless options?
548 unless isArray(array)
549 vError = new InvalidArgumentError("batch(array) requires an array argument")
550 return callback(vError)
551 for e in array
552 continue unless typeof e is "object"
553 return callback(err) if err = @_checkKey(e.type, "type")
554 return callback(err) if err = @_checkKey(e.key, "key")
555 @_batch array, options, callback
556 batch: (array, options, callback) ->
557 return @_chainedBatch() unless arguments.length
558 if isFunction options
559 callback = options
560 options = {}
561 callback = array if isFunction array
562 if callback
563 @batchAsync array, options, callback
564 else
565 @batchSync array, options
566
567
568 #TODO: remove from here, not a necessary primitive
569 approximateSizeAsync: (start, end, callback) ->
570 start = String(start) unless @_isBuffer(start)
571 end = String(end) unless @_isBuffer(end)
572 @_approximateSize start, end, callback
573 approximateSize: (start, end, callback) ->
574 if not start? or not end? or isFunction(start) or isFunction(end)
575 throw new InvalidArgumentError("approximateSize() requires valid `start`, `end` and `callback`(for async) arguments")
576 if callback
577 @approximateSizeAsync start, end, callback
578 else
579 @approximateSizeSync start, end
580
581 #should override this to test sync or if you do not wanna implement the _iterator function.
582 IteratorClass: AbstractIterator
583 iterator: (options) ->
584 options = {} unless typeof options is "object"
585 if @IteratorClass
586 return new @IteratorClass(this, options)
587 else if isFunction @_iterator
588 console.error "_iterator is deprecated. please use the IteratorClass instead."
589 return @_iterator(options)
590 throw new NotImplementedError()
591
592 _chainedBatch: ->
593 new AbstractChainedBatch(this)
594
595 _isBuffer: (obj) ->
596 Buffer.isBuffer obj
597
598 _checkKey: (obj, type) ->
599 if not obj?
600 return new InvalidArgumentError(type + " cannot be `null` or `undefined`")
601 if @_isBuffer(obj)
602 return new InvalidArgumentError(type + " cannot be an empty Buffer") if obj.length is 0
603 else
604 return new InvalidArgumentError(type + " cannot be an empty String") if String(obj) is ""
605
606 isOpen: ->
607 !!@_opened
608
609module.exports.AbstractNoSQL = AbstractNoSQL
610module.exports.__defineGetter__ "AbstractLevelDOWN", ->
611 console.error "AbstractLevelDOWN is deprecated. use AbstractNoSQL instead."
612 AbstractNoSQL
613module.exports.__defineGetter__ "AbstractIterator", ->
614 console.error "AbstractIterator is deprecated. it's moved to abstract-iterator."
615 console.error "first `npm install abstract-iterator`" unless AbstractIterator
616 AbstractIterator
617#module.exports.AbstractIterator = AbstractIterator
618module.exports.AbstractChainedBatch = AbstractChainedBatch