fdboost
Version:
Performance enhanced utilities for FoundationDB
315 lines (266 loc) • 10.5 kB
text/coffeescript
{EventEmitter} = require('events')
resolveKey = (k) ->
if (k)
if (k.key)
return if typeof(k.key) is 'function' then k.key() else k.key
else if k.rawPrefix
return k.rawPrefix
else
return k
return
###*
* Get a Reader class to perform a range read operation over the database
* @method
* @param {object} FDBBoost FDBBoost instance.
* @return {Reader} Reader
###
module.exports = (fdb, debug) ->
db = fdb.open()
###*
* The callback format for the iterate method
* @callback iterateCallback
* @param {Error} error An error instance representing the error during the execution.
* @param {Result} result A Result value on completion of the iteration.
###
###*
* Iterate over the range results
* @method
* @param {object} tr Transaction.
* @param {object} reader RangeReader instance.
* @param {string} iteratorType batch|each|array.
* @param {iterateCallback} callback Callback.
* @fires RangeReader#data
* @return {undefined}
###
iterate = (tr, reader, iteratorType, callback) ->
debug (writer) ->
writer.buffer('iteratorType', iteratorType)
getIteratorCallback = (err, iterator) ->
if (err)
callback(err)
else
debug (writer) ->
writer.log('iterate')
switch iteratorType
when 'array' then reader.toArray(iterator, callback)
when 'batch' then reader.forEachBatch(iterator, callback)
when 'each' then reader.forEach(iterator, callback)
return
reader.getIterator(tr, getIteratorCallback)
return
transactionalIterate = fdb.transactional(iterate)
fdb.RangeReader = class RangeReader extends EventEmitter
###*
* Creates a new Reader instance
* @class
* @param {object} options Settings.
* @param {(Buffer|fdb.KeySelector)} [options.begin] First key in the reader range.
* @param {(Buffer|fdb.KeySelector)}} [options.end=undefined] Last key in the reader range.
* @param {number} [options.limit=undefined] Only the first limit keys (and their values) in the range will be returned.
* @param {boolean} [options.reverse=undefined] Specified if the keys in the range will be returned in reverse order
* @param {(iterator|want_all|small|medium|large|serial|exact)} [options.streamingMode=undefined] fdb.streamingMode property that permits the API client to customize performance tradeoff by providing extra information about how the iterator will be used.
* @param {boolean} [options.nonTransactional=false] Reset transaction on expiry and start.
* @param {boolean} [options.snapshot=false] Defines whether range reads should be snapshot reads.
* @property {array} instances Collection of Document Layer db instances.
* @property {number} index Current index of the instances collection.
* @property {(Buffer|fdb.KeySelector)}} begin First key in the reader range.
* @property {(Buffer|fdb.KeySelector)}} end Last key in the reader range.
* @property {Buffer} marker Marker key for transaction expiration continuation point.
* @property {number} limit Only the first limit keys (and their values) in the range will be returned.
* @property {boolean} reverse Specified if the keys in the range will be returned in reverse order
* @property {(iterator|want_all|small|medium|large|serial|exact)} streamingMode fdb.streamingMode property that permits the API client to customize performance tradeoff by providing extra information about how the iterator will be used.
* @property {boolean} nonTransactional Reset transaction on expiry and start.
* @property {boolean} snapshot Defines whether range reads should be snapshot reads.
* @return {Reader} a Reader instance.
###
constructor: (options) ->
super()
@begin = options.begin
@end = options.end
@marker = null
@limit = options.limit
@reverse = options.reverse
@streamingMode = options.streamingMode
@nonTransactional = options.nonTransactional || false
@snapshot = options.snapshot || false
debug (writer) ->
writer.buffer('begin', resolveKey(options.begin).toString('utf8')) if options.begin
writer.buffer('end', resolveKey(options.end).toString('utf8')) if options.end
writer.buffer('limit', options.limit)
writer.buffer('reverse', options.reverse)
writer.buffer('streamingMode', options.streamingMode)
writer.buffer('nonTransactional', options.nonTransactional)
writer.buffer('snapshot', options.snapshot)
@on 'data', (data) =>
if (data instanceof Array)
@marker = kv.key for kv in data
else
@marker = data.key
return
###*
* The callback format for the getLastKey method
* @callback getLastKeyCallback
* @param {Error} error An error instance representing the error during the execution.
* @param {Buffer} lastKey The Buffer value if the getLastKey method was successful.
###
###*
* Get the last key of the range if no end key is provided to the RangeReader
* @method
* @param {object} tr Transaction.
* @param {object} reader RangeReader instance.
* @param {getLastKeyCallback} callback Callback.
* @return {undefined}
###
getLastKey: (tr, callback) ->
if (@end)
debug (writer) ->
writer.buffer('end', @end.key.toString('utf8'))
callback(null, @end)
else
tr.getLastKey(@begin, callback)
return
###*
* The callback format for the init method
* @callback initCallback
* @param {Error} error An error instance representing the error during the execution.
###
###*
* Initialize the reader before iteration
* @abstract
* @param {object} tr Transaction.
* @param {object} reader RangeReader instance.
* @param {initCallback} callback Callback.
* @return {undefined}
###
init: (tr, callback) ->
callback(null)
###*
* The callback format for the getIterator method
* @callback getIteratorCallback
* @param {Error} error An error instance representing the error during the execution.
* @param {LazyIterator} iterator The LazyIterator instance if the getIterator method was successful.
###
###*
* Get a LazyIterator instance for the current range read operation
* @method
* @param {object} tr Transaction.
* @param {object} reader RangeReader instance.
* @param {getIteratorCallback} callback Callback.
* @return {undefined}
###
getIterator: (tr, callback) ->
debug (writer) ->
writer.log('getIterator')
options =
limit: @limit
reverse: @reverse
streamingMode: @streamingMode
ts = if @snapshot then tr.snapshot else tr
if (@end || @marker)
begin = if @marker then fdb.KeySelector.firstGreaterThan(@marker) else @begin
@getLastKey tr, (err, lastKey) ->
if (err)
callback(err)
else
iterator = ts.getRange(begin, lastKey, options)
debug (writer) ->
writer.log('getLastKey')
writer.buffer('method', 'getRange')
writer.buffer('begin', resolveKey(begin).toString('utf8'))
writer.buffer('end', resolveKey(lastKey).toString('utf8'))
writer.buffer('options', options)
callback(null, iterator)
return
else
iterator = ts.getRangeStartsWith(@begin, options)
debug (writer) =>
writer.buffer('method', 'getRangeStartsWith')
writer.buffer('prefix', resolveKey(@begin).toString('utf8'))
writer.buffer('options', options)
callback(null, iterator)
return
###*
* Iterate over array results
* @virtual
* @param {LazyIterator} iterator LazyIterator instance.
* @param {iterateCallback} callback Callback.
* @fires RangeReader#data
* @return {undefined}
###
toArray: (iterator, callback) ->
iterator.toArray (err, arr) =>
@emit('data', arr)
callback(err)
return
return
###*
* Iterate over batch results
* @virtual
* @param {LazyIterator} iterator LazyIterator instance.
* @param {iterateCallback} callback Callback.
* @fires RangeReader#data
* @return {undefined}
###
forEachBatch: (iterator, callback) ->
func = (arr, next) =>
@emit('data', arr)
next()
return
iterator.forEachBatch(func, callback)
return
###*
* Iterate over key-value pair results
* @virtual
* @param {LazyIterator} iterator LazyIterator instance.
* @param {iterateCallback} callback Callback.
* @fires RangeReader#data
* @return {undefined}
###
forEach: (iterator, callback) ->
func = (kv, next) =>
@emit('data', kv)
next()
return
iterator.forEach(func, callback)
return
###*
* Execute the reader using an iterator type
* @virtual
* @param {object} [tr=null] transaction.
* @param {string} iteratorType batch|each|array.
* @fires RangeReader#error
* @fires RangeReader#continue
* @fires RangeReader#end
* @return {undefined}
###
execute: (tr, iteratorType) ->
if (typeof(tr) is 'string')
iteratorType = tr
tr = db
debug (writer) ->
writer.log('execute')
complete = (err, result) =>
if (err)
@emit('error', err)
else
@emit('end', result)
return
toBeContinued = =>
debug (writer) ->
writer.log('continue')
tr.reset()
@emit('continue')
txi()
return
pastVersionCatchingCallback = (err, result) =>
if (err && @nonTransactional && err.message is 'past_version')
toBeContinued()
else
complete(err, result)
return
txi = =>
transactionalIterate(tr || db, @, iteratorType, pastVersionCatchingCallback)
return
txi()
return
return