public Block(StorageOptions storageOptions, ReadOptions readOptions, BlockHandle handle, FileData fileData)
		{
			try
			{
				_handle = handle;
				_storageOptions = storageOptions;
				_fileData = fileData;
				if (handle.Position > fileData.Size || (handle.Position + handle.Count + BlockTrailerSize) > fileData.Size)
					throw new CorruptedDataException("The specified accessor is beyond the bounds of the provided mappedFile");

				_accessor = _fileData.File.CreateAccessor(handle.Position, handle.Count + BlockTrailerSize);

				if (readOptions.VerifyChecksums)
				{
					var crc = Crc.Unmask(_accessor.ReadInt32(handle.Count + 1));
					var actualCrc = CalculateActualCrc(handle.Count + 1); // data + tag
					if (crc != actualCrc)
						throw new CorruptedDataException("block checksum mismatch");
				}
				RestartsCount = _accessor.ReadInt32(handle.Count - sizeof(int));
				RestartsOffset = handle.Count - (RestartsCount * sizeof(int)) - sizeof(int);
				if (RestartsOffset > handle.Count)
					throw new CorruptedDataException("restart offset wrapped around");
			}
			catch (Exception)
			{
				Dispose();
				throw;
			}
		}
		public DbIterator NewIterator(ReadOptions options)
		{
			var result = state.NewInternalIterator(options);
			var internalIterator = result.Item1;
			var latestSnapshot = result.Item2;

			return new DbIterator(state, internalIterator, options.Snapshot != null ? options.Snapshot.Sequence : latestSnapshot);
		}
		public TwoLevelIterator(
			IIterator indexIterator,
			Func<ReadOptions, BlockHandle, IIterator> getIterator,
			ReadOptions readOptions
		)
		{
			_indexIterator = indexIterator;
			_readOptions = readOptions;
			this.getIterator = getIterator;
		}
		public Stream Read(Slice key, ReadOptions options = null)
		{
			if (options == null)
			{
				options = new ReadOptions();
			}

			var mem = state.MemTable;
			var imm = state.ImmutableMemTable;
			var currentVersion = state.VersionSet.Current;

			var snapshot = options.Snapshot != null ? options.Snapshot.Sequence : state.VersionSet.LastSequence;

			var reference = new Reference<Slice> { Value = key };

			state.PerfCounters.Read();

			Stream stream;
			GetStats stats;

			if (mem.TryGet(reference.Value, snapshot, out stream))
			{
				state.PerfCounters.BytesRead(stream.Length);
				return stream;
			}

			if (imm != null && imm.TryGet(reference.Value, snapshot, out stream))
			{
				state.PerfCounters.BytesRead(stream.Length); 
				return stream;
			}

			if (currentVersion.TryGet(reference.Value, snapshot, options, out stream, out stats))
			{
				if (currentVersion.UpdateStats(stats))
				{
					Background.Work(MaybeScheduleCompactionAsync());
				}

				state.PerfCounters.BytesRead(stream.Length); 
				return stream;
			}

			return null;
		}
		public IIterator NewIterator(ReadOptions options, ulong fileNumber, long fileSize)
		{
			try
			{
				var table = FindTable(fileNumber, fileSize);

				return table.CreateIterator(options);
			}
			catch (Exception e)
			{
				log.InfoException("Could not open iterator for " + fileNumber + ", will return empty iterator", e);

				if (state.Options.ParanoidChecks)
					throw;

				return new EmptyIterator();
			}
		}
		internal IIterator GetFileIterator(ReadOptions readOptions, BlockHandle handle)
		{
			var fileNumber = (ulong)handle.Position;
			var fileSize = handle.Count;

			return storageContext.TableCache.NewIterator(readOptions, fileNumber, fileSize);
		}
		public IIterator MakeInputIterator(Compaction compaction)
		{
			var readOptions = new ReadOptions
								  {
									  VerifyChecksums = storageContext.Options.ParanoidChecks,
									  FillCache = false
								  };

			// Level-0 files have to be merged together.  For other levels,
			// we will make a concatenating iterator per level.
			// TODO(opt): use concatenating iterator for level-0 if there is no overlap
			var list = new List<IIterator>();
			for (int which = 0; which < 2; which++)
			{
				if (compaction.Inputs[which].Count != 0)
				{
					if (compaction.Level + which == 0)
					{
						var files = new List<FileMetadata>(compaction.Inputs[which]);
						list.AddRange(files.Select(file => storageContext.TableCache.NewIterator(readOptions, file.FileNumber, file.FileSize)));
					}
					else
					{
						// Create concatenating iterator for the files from this level
						list.Add(new TwoLevelIterator(
							         new LevelFileNumIterator(storageContext.InternalKeyComparator, compaction.Inputs[which]),
							         GetFileIterator, readOptions));
					}
				}
			}

			return NewMergingIterator(storageContext.InternalKeyComparator, list);
		}
		public ItemState Get(InternalKey key, ulong fileNumber, long fileSize, ReadOptions readOptions, IComparator comparator,
							 out Stream stream)
		{
			Table table = FindTable(fileNumber, fileSize);

			Tuple<Slice, Stream> result = table.InternalGet(readOptions, key);

			stream = null;

			if (result == null)
			{
				return ItemState.NotFound;
			}

			bool shouldDispose = true;
			try
			{
				InternalKey internalKey;
				if (!InternalKey.TryParse(result.Item1, out internalKey))
				{
					return ItemState.Corrupt;
				}

				if (comparator.Compare(internalKey.UserKey, key.UserKey) == 0)
				{
					bool isFound = internalKey.Type == ItemType.Value;
					if (!isFound)
					{
						return ItemState.Deleted;
					}

					stream = result.Item2;
					shouldDispose = false;
					return ItemState.Found;
				}

				return ItemState.NotFound;
			}
			finally
			{
				if (shouldDispose && result.Item2 != null)
					result.Item2.Dispose();
			}
		}
		public bool TryGet(Slice key, ulong seq, ReadOptions readOptions, out Stream stream, out GetStats stats)
		{
			stats = new GetStats
						{
							SeekFile = null,
							SeekFileLevel = -1
						};

			FileMetadata lastFileRead = null;
			int lastFileReadLevel = -1;

			var internalKey = new InternalKey(key, seq, ItemType.ValueForSeek);

			// We can search level-by-level since entries never hop across
			// levels.  Therefore we are guaranteed that if we find data
			// in an smaller level, later levels are irrelevant.
			for (var level = 0; level < Config.NumberOfLevels; level++)
			{
				if (Files[level].Count == 0)
				{
					continue;
				}

				// Get the list of files to search in this level
				var files = GetRelevantFilesForLevel(level, internalKey);
				if (files == null || files.Count == 0)
					continue;

				foreach (var f in files)
				{
					if (lastFileRead != null && stats.SeekFile == null)
					{
						// We have had more than one seek for this read.  Charge the 1st file.
						stats.SeekFile = lastFileRead;
						stats.SeekFileLevel = lastFileReadLevel;
					}

					lastFileRead = f;
					lastFileReadLevel = level;

					var state = storageContext.TableCache.Get(
						internalKey, 
						f.FileNumber, 
						f.FileSize, 
						readOptions,
						storageContext.InternalKeyComparator.UserComparator,
						out stream);

					switch (state)
					{
						case ItemState.Found:
							return true;
						case ItemState.NotFound:
							break;
						case ItemState.Deleted:
							return false;
						case ItemState.Corrupt:
							return false;
						default:
							throw new NotSupportedException(state.ToString());
					}
				}
			}

			stream = null;
			return false;
		}
		private IIterator CreateFileIterator(FileMetadata file)
		{
			var readOptions = new ReadOptions
			{
				VerifyChecksums = state.Options.ParanoidChecks,
				FillCache = false
			};

			return state.TableCache.NewIterator(readOptions, file.FileNumber, file.FileSize);
		}
		private IIterator CreateInputIterator(CompactionState compactionState)
		{
			var readOptions = new ReadOptions
			{
				VerifyChecksums = state.Options.ParanoidChecks,
				FillCache = false
			};

			return new MergingIterator(state.InternalKeyComparator, compactionState.Compaction.Inputs[0].Select(x => state.TableCache.NewIterator(readOptions, x.FileNumber, x.FileSize)).ToList());
		}
		public Tuple<IIterator, ulong> NewInternalIterator(ReadOptions options)
		{
			var mem = MemTable;
			var imm = ImmutableMemTable;
			var currentVersion = VersionSet.Current;

			var snapshot = options.Snapshot != null ? options.Snapshot.Sequence : VersionSet.LastSequence;

			var iterators = new List<IIterator>
				                {
					                mem.NewIterator()
				                };

			if (imm != null)
				iterators.Add(imm.NewIterator());

			// Merge all level zero files together since they may overlap
			iterators.AddRange(currentVersion.Files[0].Select(file => TableCache.NewIterator(options, file.FileNumber, file.FileSize)));

			// For levels > 0, we can use a concatenating iterator that sequentially
			// walks through the non-overlapping files in the level, opening them
			// lazily.
			for (var level = 1; level < Config.NumberOfLevels; level++)
			{
				if (currentVersion.Files[level].Count > 0)
					iterators.Add(new TwoLevelIterator(new LevelFileNumIterator(InternalKeyComparator, currentVersion.Files[level]), VersionSet.GetFileIterator, options));
			}

			var internalIterator = new MergingIterator(InternalKeyComparator, iterators);

			return new Tuple<IIterator, ulong>(internalIterator, snapshot);
		}
		/// <summary>
		/// Returns a new iterator over the table contents.
		/// The result of NewIterator() is initially invalid (caller must
		/// call one of the Seek methods on the iterator before using it).
		/// </summary>
		public IIterator CreateIterator(ReadOptions readOptions)
		{
			return new TwoLevelIterator(_indexBlock.CreateIterator(_storageState.Options.Comparator), CreateBlockIterator, readOptions);
		}
		public Table(StorageState storageState, FileData fileData)
		{
			_storageState = storageState;
			try
			{
				_fileData = fileData;

				if (_storageState.Options.MaxBlockCacheSizePerTableFile > 0)
				{
					_blockCache = new LruCache<BlockHandle, Block>(_storageState.Options.MaxBlockCacheSizePerTableFile);
				}

				if (fileData.Size < Footer.EncodedLength)
					throw new CorruptedDataException("File is too short to be an sstable");

				var footer = new Footer();
				using (var accessor = fileData.File.CreateAccessor(fileData.Size - Footer.EncodedLength, Footer.EncodedLength))
				{
					footer.DecodeFrom(accessor);
				}

				var readOptions = new ReadOptions
					{
						VerifyChecksums = _storageState.Options.ParanoidChecks
					};
				_indexBlock = new Block(_storageState.Options, readOptions, footer.IndexHandle, fileData);
				_indexBlock.IncrementUsage();
				if (_storageState.Options.FilterPolicy == null)
					return; // we don't need any metadata

				using (var metaBlock = new Block(_storageState.Options, readOptions, footer.MetaIndexHandle, fileData))
				using (var iterator = metaBlock.CreateIterator(CaseInsensitiveComparator.Default))
				{
					var filterName = ("filter." + _storageState.Options.FilterPolicy.Name);
					iterator.Seek(filterName);
					if (iterator.IsValid && CaseInsensitiveComparator.Default.Compare(filterName, iterator.Key) == 0)
					{
						var handle = new BlockHandle();
						using (var stream = iterator.CreateValueStream())
						{
							handle.DecodeFrom(stream);
						}
						var filterAccessor = _fileData.File.CreateAccessor(handle.Position, handle.Count);
						try
						{
							_filter = _storageState.Options.FilterPolicy.CreateFilter(filterAccessor);
						}
						catch (Exception)
						{
							if (_filter == null)
								filterAccessor.Dispose();
							else
								_filter.Dispose();
							throw;
						}

					}
				}
			}
			catch (Exception)
			{
				Dispose();
				throw;
			}
		}
		internal IIterator CreateBlockIterator(ReadOptions readOptions, BlockHandle handle)
		{
			if (_blockCache == null)
			{
				Block uncachedBlock = null;
				IIterator blockIterator = null;
				try
				{
					uncachedBlock = new Block(_storageState.Options, readOptions, handle, _fileData);
					// uncachedBlock.IncrementUsage(); - intentionally not calling this, will be disposed when the iterator is disposed
					blockIterator = uncachedBlock.CreateIterator(_storageState.InternalKeyComparator);
					return blockIterator;
				}
				catch (Exception)
				{
					if (uncachedBlock != null)
						uncachedBlock.Dispose();
					if (blockIterator != null)
						blockIterator.Dispose();
					throw;
				}
			}

			Block value;
			if (_blockCache.TryGet(handle, out value))
			{
				return value.CreateIterator(_storageState.InternalKeyComparator);
			}
			var block = new Block(_storageState.Options, readOptions, handle, _fileData);
			block.IncrementUsage(); // the cache is using this, so avoid having it disposed by the cache while in use
			_blockCache.Set(handle, block);
			return block.CreateIterator(_storageState.InternalKeyComparator);
		}
		internal Tuple<Slice, Stream> InternalGet(ReadOptions readOptions, InternalKey key)
		{
			using (var iterator = _indexBlock.CreateIterator(_storageState.InternalKeyComparator))
			{
				iterator.Seek(key.TheInternalKey);
				if (iterator.IsValid == false)
					return null;
				var handle = new BlockHandle();
				using (var stream = iterator.CreateValueStream())
				{
					handle.DecodeFrom(stream);
				}
				if (_filter != null && _filter.KeyMayMatch(handle.Position, key.UserKey) == false)
				{
					return null; // opptimized not found by filter, no need to read the actual block
				}
				using (var blockIterator = CreateBlockIterator(readOptions, handle))
				{
					blockIterator.Seek(key.TheInternalKey);
					if (blockIterator.IsValid == false)
						return null;
					return Tuple.Create(blockIterator.Key, blockIterator.CreateValueStream());
				}
			}
		}