/**
 * Modified from bcoin's bdb (https://github.com/bcoin-org/bdb) (MIT LICENSE).
 * Credit to the original author, Christopher Jeffrey (https://github.com/chjj).
 */ import { BIG_ENDIAN , BigNumber , Endianness , getLogger , ZERO , } from '@pigi/core-utils' ; import { RangeBucket , RangeEntry , Bucket , Batch , DB , IteratorOptions , KV , PUT_BATCH_TYPE , RangeIterator , } from '../types' ; import { BaseRangeIterator } from './iterator' ; import { intersects } from './utils' ; const log = getLogger ( 'range-db' ) ; /**
 * Simple bucket implementation that forwards all
 * calls up to the database but appends a prefix.
 */ export class BaseRangeBucket implements RangeBucket { param db param prefix param keyLength param endianness constructor ( readonly db , readonly prefix , readonly keyLength = 16 , readonly endianness = BIG_ENDIAN ) { } param value returns private addPrefix ( value ) { return value !== undefined ? Buffer . concat ( [ this . prefix , value ] ) : this . prefix ; } param start param value returns private addStartToValue ( start , value ) { return Buffer . concat ( [ start . toBuffer ( this . endianness , this . keyLength ) , value , ] ) ; } param value returns private getStartFromValue ( value ) { return value . slice ( 0 , this . keyLength ) ; } param value returns private getDataFromValue ( value ) { return value . slice ( this . keyLength ) ; } param start param value returns private bnToKey ( bigNum ) { const buf = bigNum . toBuffer ( this . endianness , this . keyLength ) ; return Buffer . concat ( [ this . prefix , buf ] ) ; } param key returns private isCorrectPrefix ( key ) { if ( typeof key === 'undefined' ) { return false ; } return Buffer . compare ( this . prefix , key . slice ( 0 , this . prefix . length ) ) === 0 ; } param start param end returns private validateRange ( start , end ) { if ( ! start . lt ( end ) ) { throw new Error ( 'Start not less than end' ) ; } if ( ! start . gte ( ZERO ) ) { throw new Error ( 'Start less than zero' ) ; } } param result returns private resultToRange ( result ) { return { start : new BigNumber ( this . getStartFromValue ( result . value ) ) , end : new BigNumber ( result . key . slice ( this . prefix . length ) ) , value : this . getDataFromValue ( result . value ) , } ; } param start param end returns private async delBatchOps ( start , end ) { this . validateRange ( start , end ) ; const ranges = await this . get ( start , end ) ; const batchOps = [ ] ; for ( const range of ranges ) { batchOps . push ( { type : 'del' , key : this . bnToKey ( range . end ) , } ) ; } return { ranges , batchOps } ; } param start param end param value public async put ( start , end , value ) { this . validateRange ( start , end ) ; log . debug ( 'Putting range: [' , start . toString ( 16 ) , ',' , end . toString ( 16 ) , ') with value:' , value ) ; const { ranges , batchOps } = await this . delBatchOps ( start , end ) ; if ( ranges . length > 0 && start . gt ( ranges [ 0 ] . start ) ) { batchOps . push ( { type : PUT_BATCH_TYPE , key : this . bnToKey ( start ) , value : this . addStartToValue ( ranges [ 0 ] . start , ranges [ 0 ] . value ) , } ) ; } if ( ranges . length > 0 && ranges [ ranges . length - 1 ] . end . gt ( end ) ) { batchOps . push ( { type : PUT_BATCH_TYPE , key : this . bnToKey ( ranges [ ranges . length - 1 ] . end ) , value : this . addStartToValue ( end , ranges [ ranges . length - 1 ] . value ) , } ) ; } batchOps . push ( { type : PUT_BATCH_TYPE , key : this . bnToKey ( end ) , value : this . addStartToValue ( start , value ) , } ) ; await this . db . batch ( batchOps ) ; } param start param end returns public async del ( start , end ) { const { ranges , batchOps } = await this . delBatchOps ( start , end ) ; await this . db . batch ( batchOps ) ; return ranges ; } public async hasDataInRange ( start , end ) { return ( await this . get ( start , end ) ) . length > 0 ; } param start param end returns public async get ( start , end ) { this . validateRange ( start , end ) ; log . debug ( 'Getting range: [' , start . toString ( 16 ) , ',' , end . toString ( 16 ) , ')' ) ; const it = this . db . iterator ( { gt : this . bnToKey ( start ) , keyAsBuffer : true , } ) ; const ranges = [ ] ; let result = await it . next ( ) ; if ( ! this . isCorrectPrefix ( result . key ) ) { await it . end ( ) ; return [ ] ; } const queryStart = this . bnToKey ( start ) ; const queryEnd = this . bnToKey ( end ) ; let resultStart = this . addPrefix ( this . getStartFromValue ( result . value ) ) ; let resultEnd = result . key ; while ( intersects ( queryStart , queryEnd , resultStart , resultEnd ) && this . isCorrectPrefix ( result . key ) ) { ranges . push ( this . resultToRange ( result ) ) ; result = await it . next ( ) ; if ( typeof result . key === 'undefined' ) { break ; } resultStart = this . addPrefix ( this . getStartFromValue ( result . value ) ) ; resultEnd = result . key ; } await it . end ( ) ; return ranges ; } param options returns public iterator ( options = { } ) { const rangeIterator = new BaseRangeIterator ( this . db , { ... options , prefix : this . addPrefix ( options . prefix ) , } , ( res ) => this . resultToRange ( res ) ) ; return rangeIterator ; } param prefix returns public bucket ( prefix ) { return this . db . bucket ( this . addPrefix ( prefix ) ) ; } param prefix returns public rangeBucket ( prefix ) { return this . db . rangeBucket ( this . addPrefix ( prefix ) ) ; } }