/**
 * Copyright (c) 2018-present, heineiuo.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import { FileHandle } from "./Env" ; import Slice from "./Slice" ; import Footer from "./SSTableFooter" ; import DataBlock from "./SSTableBlock" ; import FilterBlock from "./SSTableFilterBlock" ; import { BlockContents , BlockHandle , CompressionTypes , kBlockTrailerSize , InternalKey , Entry , } from "./Format" ; import Status from "./Status" ; import { Options , ReadOptions , defaultReadOptions } from "./Options" ; import { assert } from "./DBHelper" ; import { Buffer } from "./Buffer" ; import { encodeFixed64 } from "./Coding" ; export default class SSTable { static async readBlock ( file , options , handle ) { const result = { data : new Slice ( ) , cachable : false , heapAllocated : false , } as BlockContents ; const n = handle . size ; const data = Buffer . alloc ( handle . size + kBlockTrailerSize ) ; const { bytesRead } = await file . read ( data , 0 , data . length , handle . offset ) ; if ( bytesRead !== handle . size + kBlockTrailerSize ) { throw new Error ( "truncated block read" ) ; } switch ( data [ n ] ) { case CompressionTypes . none : result . data = new Slice ( data . slice ( 0 , n ) ) ; break ; default : throw new Error ( "bad block type" ) ; } return result ; } static async open ( options , file ) { const stat = await file . stat ( ) ; if ( stat . size < Footer . kEncodedLength ) { throw new Error ( "file is too short to be an sstable" ) ; } const footerBuf = Buffer . alloc ( Footer . kEncodedLength ) ; await file . read ( footerBuf , 0 , footerBuf . length , stat . size - Footer . kEncodedLength ) ; const footer = new Footer ( footerBuf ) ; const indexBlockContents = await this . readBlock ( file , defaultReadOptions , footer . indexHandle ) ; const indexBlock = new DataBlock ( indexBlockContents ) ; indexBlock . blockType = "indexblock" ; const table = new SSTable ( { file , options , indexBlock , metaIndexHandle : footer . metaIndexHandle , } ) ; await table . readMeta ( footer ) ; return table ; } constructor ( rep ) { this . _file = rep . file ; this . _options = rep . options ; this . _indexBlock = rep . indexBlock ; this . _cacheId = rep . options . blockCache . newId ( ) ; } private _file : FileHandle ; private _cacheId : bigint ; private _options : Options ; private _indexBlock : DataBlock ; private _filterReader ! : FilterBlock ; private async readMeta ( footer ) { if ( ! this . _options . filterPolicy ) { return ; } const contents = await SSTable . readBlock ( this . _file , defaultReadOptions , footer . metaIndexHandle ) ; const meta = new DataBlock ( contents ) ; meta . blockType = "metaindexblock" ; const key = new Slice ( "filter." + this . _options . filterPolicy . name ( ) ) ; for ( const entry of meta . iterator ( this . _options . comparator ) ) { if ( entry . key . isEqual ( key ) ) { await this . readFilter ( entry . value . buffer ) ; } } } private async readFilter ( filterHandleBuffer ) { const filterHandle = BlockHandle . from ( filterHandleBuffer ) ; const readOptions = { ... defaultReadOptions } ; const block = await SSTable . readBlock ( this . _file , readOptions , filterHandle ) ; this . _filterReader = new FilterBlock ( this . _options . filterPolicy , block . data ) ; } public async get ( target ) { const targetInternalKey = InternalKey . from ( target ) ; for ( const handleValue of this . _indexBlock . iterator ( this . _options . comparator ) ) { const handle = BlockHandle . from ( handleValue . value . buffer ) ; if ( ! ! this . _filterReader && ! this . _filterReader . keyMayMatch ( handle . offset , target ) ) { } else { for await ( const entry of this . blockIterator ( this , this . _options , handle , false , "datablock" ) ) { const entryInternalKey = InternalKey . from ( entry . key ) ; if ( entryInternalKey . userKey . isEqual ( targetInternalKey . userKey ) && entryInternalKey . sequence <= targetInternalKey . sequence ) { return new Status ( Promise . resolve ( entry ) ) ; } } } } return Status . createNotFound ( ) ; } async * entryIterator ( reverse = false ) { for ( const handleValue of this . _indexBlock . iterator ( this . _options . comparator , reverse ) ) { const handle = BlockHandle . from ( handleValue . value . buffer ) ; yield * this . blockIterator ( this , this . _options , handle , reverse , "datablock" ) ; } } async * blockIterator ( table , options , handle , reverse = false , blockType ? ) { const key = Buffer . concat ( [ encodeFixed64 ( this . _cacheId ) , encodeFixed64 ( handle . offset ) , ] ) ; let dataBlock = this . _options . blockCache . get ( key ) ; if ( ! dataBlock ) { const data = Buffer . alloc ( handle . size ) ; const { bytesRead } = await this . _file . read ( data , 0 , data . length , handle . offset ) ; assert ( bytesRead === data . length ) ; const contents = { data : new Slice ( data ) , } as BlockContents ; dataBlock = new DataBlock ( contents ) ; if ( blockType ) dataBlock . blockType = blockType ; this . _options . blockCache . set ( key , dataBlock ) ; } yield * dataBlock . iterator ( options . comparator , reverse ) ; } }