/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Text; using JetBrains.Omea.Base; using JetBrains.Omea.Containers; using JetBrains.Omea.Diagnostics; using System.Threading; using JetBrains.DataStructures; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea.Database { internal enum RecordType { Normal = 0, Deleted = 1, Updated = 2, New = 3, NormalMarker = 'x', DeletedMarker = 'y', CorruptedMarker = 'ж', } internal class Table : ITable, ITableDesign { #region class members private Database _database; private HashMap _columnsMap = new HashMap(); private ArrayList _indexes = new ArrayList(); private ArrayList _compoundIndexes = new ArrayList(); private ArrayList _compoundIndexesWithValue = new ArrayList(); private IntHashTable _indexesCompoundMap = new IntHashTable(); private IntHashTable _indexesCompoundMapWithValue = new IntHashTable(); private CachedStream _file = null; private SafeBinaryReader _reader = null; private SafeBinaryWriter _writer = null; private int _lastCommited = -1; private TableStructure _tblStructure = null; private Tracer _tracer; private Column _idColumn = null; private Compound _compound = new Compound( null, null ); private Compound _compoundSecond = new Compound( null, null ); private CompoundAndValue _compoundAndValue = new CompoundAndValue( null, null, null ); private CompoundAndValue _compoundAndValueSecond = new CompoundAndValue( null, null, null ); private Column[] _columns = new Column[0]; private object[] _fields = new object[0]; private string _fileName; private bool _canUpdate = true; private bool _fixedSize = true; private bool _isRecordWithBLOB = false; private bool[] _fieldChangeFlag = new bool[0]; private int _totalCount = -1; private int _sortedColumn = -1; private static ICountedResultSet _emptyResultSet = new EmptyResultSet(); private int _loadedRecords = 0, _savedRecords = 0; private long _loadedRecordSize = 0, _savedRecordSize = 0; private bool _autoFlush = true; #endregion internal Table( Database database, TableStructure tblStructure ) { _database = database; _tblStructure = tblStructure; _totalCount = _tblStructure.TotalCount(); OpenStreams(); _tracer = new Tracer( "(DBUtils) Table - " + Name ); } public void CheckTableLength() { if ( _fixedSize ) { long remainder = _file.Length % _recordSize; if ( remainder != 0 ) { _file.Position = _file.Length; byte[] padding = new byte[_recordSize-remainder]; _writer.Write( padding ); _writer.Flush(); } } } public bool NeedUpgradeTo22 { get { return _checkedNeedUpgradeTo22 ? false : _database.NeedUpgradeTo22; } } private bool _checkedNeedUpgradeTo22 = false; private void CheckedNeedUpgradeTo22() { _checkedNeedUpgradeTo22 = true; } public bool AutoFlush { get { return _autoFlush; } set { _autoFlush = value; } } public void FlushData() { _writer.Flush(); } public long TracePerformanceCounters() { Trace.WriteLine( Name + " performance counters: " ); long result = _loadedRecordSize + _savedRecordSize; Trace.WriteLine( " Loaded " + _loadedRecords + " records (" + Utils.SizeToString( _loadedRecordSize ) + ")" ); Trace.WriteLine( " Saved " + _savedRecords + " records (" + Utils.SizeToString( _savedRecordSize ) + ")" ); foreach( IDBIndex dbIndex in _indexes ) { if ( dbIndex != null ) { result += TraceIndexPerformanceCounters( dbIndex ); } } foreach( CompoundIndex compoundIndex in _compoundIndexes ) { if ( compoundIndex != null ) { result += TraceIndexPerformanceCounters( compoundIndex._dbIndex ); } } foreach( CompoundIndexWithValue indexWithValue in _compoundIndexesWithValue ) { if ( indexWithValue != null ) { result += TraceIndexPerformanceCounters( indexWithValue._dbIndex ); } } return result; } private long TraceIndexPerformanceCounters( IDBIndex dbIndex ) { long loadedBytes = dbIndex.LoadedPages * dbIndex.PageSize; Trace.WriteLine( " " + dbIndex.Name + ": loaded " + dbIndex.LoadedPages + " pages (" + Utils.SizeToString( loadedBytes ) + ")" ); return loadedBytes; } #region Working with record private int Commit( int offset, object[] oldFields, object[] newFields ) { bool fieldsChanged = false; if ( _canUpdate && newFields != null && offset != -1 ) { for ( int i = 0; i < _fieldChangeFlag.Length; ++i ) { bool changed = !oldFields[i].Equals( newFields[i] ); if ( changed ) { fieldsChanged = true; } _fieldChangeFlag[i] = changed; } if ( !fieldsChanged ) return offset; } _tblStructure.Dirty = true; if ( offset == -1 || !_canUpdate ) { if ( _totalCount != -1 ) { _totalCount++; } _lastCommited = (int) (_file.Position = _file.Length ); } else { CheckIfOffsetValid( offset, false ); _file.Position = offset; _lastCommited = offset; } _writer.Write( (byte)RecordType.NormalMarker ); int columnCount = _columns.Length; for ( int i = 0; i < columnCount; i++ ) { Column column = _columns[i]; Object value = column.SaveValue( _writer ); object dbIndex = column.Index; if ( dbIndex != null ) { bool bAddToIndex = true; if ( _canUpdate && offset != -1 && newFields != null ) { if ( !fieldsChanged ) { continue; } bAddToIndex = _fieldChangeFlag[i]; } else if ( !_canUpdate ) { string strValue = value as string; if ( strValue != null ) { value = DBHelper.GetHashCodeInLowerCase( strValue ); } } if ( bAddToIndex ) { (dbIndex as IDBIndex).AddIndexEntry( (IComparable)value, _lastCommited ); } } } if ( _autoFlush ) { _writer.Flush(); } _savedRecords++; _savedRecordSize += _file.Position - _lastCommited; for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; Column column1 = compoundIndex._column1; Column column2 = compoundIndex._column2; Object value1 = column1.Value; Object value2 = column2.Value; bool bAddToIndex = true; if ( _canUpdate && offset != -1 && newFields != null ) { bAddToIndex = _fieldChangeFlag[compoundIndex._columnIndex1]; if ( !bAddToIndex ) { bAddToIndex = _fieldChangeFlag[compoundIndex._columnIndex2]; } } else if ( !_canUpdate ) { string strValue1 = value1 as string; if ( strValue1 != null ) { value1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = value2 as string; if ( strValue2 != null ) { value2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } } if ( bAddToIndex ) { SetCompoundKey( _compound, value1, value2 ); dbIndex.AddIndexEntry( _compound, _lastCommited ); } } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndexWithValue compoundIndexWithValue = _compoundIndexesWithValue[i] as CompoundIndexWithValue; if ( compoundIndexWithValue != null ) { IDBIndex dbIndex = compoundIndexWithValue._dbIndex; Column column1 = compoundIndexWithValue._column1; Column column2 = compoundIndexWithValue._column2; object value1 = column1.Value; object value2 = column2.Value; object value3 = compoundIndexWithValue._valueColumn.Value; bool bAddToIndex = true; if ( _canUpdate && offset != -1 && newFields != null ) { bAddToIndex = _fieldChangeFlag[compoundIndexWithValue._columnIndex1]; if ( !bAddToIndex ) { bAddToIndex = _fieldChangeFlag[compoundIndexWithValue._columnIndex2]; } if ( !bAddToIndex ) { bAddToIndex = _fieldChangeFlag[compoundIndexWithValue._fieldIndex]; } } else if ( !_canUpdate ) { string strValue1 = value1 as string; if ( strValue1 != null ) { value1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = value2 as string; if ( strValue2 != null ) { value2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } } if ( bAddToIndex ) { SetCompoundKey( _compoundAndValue, value1, value2, value3 ); dbIndex.AddIndexEntry( _compoundAndValue, _lastCommited ); } } } return _lastCommited; } public IRecord GetRecord( int offset ) { Record rec = GetRecordImpl( offset ); rec.SetFields( _fields.Clone() as object[] ); return rec; } internal Record GetRecordImpl( int offset ) { CheckIfOffsetValid( offset ); return new Record( this, offset ); } public int UpdateFields( Record record, object[] oldFields, object[] newFields, int offset, RecordType recordType ) { CheckIfSingleOffsetValid( offset, true ); if ( recordType == RecordType.Updated ) { if ( _canUpdate ) { RemoveValuesFromIndex( offset, oldFields, newFields ); } else { DeleteRecord( offset, oldFields ); } } for ( int i = 0; i < newFields.Length; i++ ) { _fields[i] = newFields[i]; } int newOffset; if ( _canUpdate ) { newOffset = Commit( offset, oldFields, newFields ); } else { newOffset = Commit( -1, oldFields, newFields ); } return newOffset; } public object[] ReadRecordFromColumns( ) { return _fields.Clone() as object[]; } public object[] ReadRecordFromOffsetNoChecking( int offset ) { CheckIfOffsetValid( offset, true ); _file.Position = offset; long startOffset = _file.Position; _file.ReadByte(); LoadRecordToColumns(); _loadedRecords++; _loadedRecordSize += _file.Position - startOffset; return _fields.Clone() as object[]; } private RecordType ReadRecord( bool fix ) { long startOffset = _file.Position; RecordType recType = (RecordType)_file.ReadByte(); try { bool success = LoadRecordToColumns( startOffset, fix ); _loadedRecords++; _loadedRecordSize += _file.Position - startOffset; if ( !success ) { return RecordType.DeletedMarker; } } catch ( EndOfStreamException ) { if ( fix ) { _file.SetLength( startOffset ); Console.WriteLine( "Table was truncated because last record is corrupted" ); } return RecordType.DeletedMarker; } catch ( NoEndMarkerException ) { return RecordType.DeletedMarker; } return recType; } /* * Method for regular reading of records. If there is data corruption then BadIndexesException is thrown. * */ private void LoadRecordToColumns( ) { for ( int i = 0; i < _columns.Length; ++i ) { try { _columns[i].LoadValue( _reader ); } catch ( DateTimeCorruptedException exception ) { throw new BadIndexesException( "Table is corrupted.", exception ); } catch ( NoEndMarkerException exception ) { throw new BadIndexesException( "Unexpected end of table. Table or indexes are corrupted.", exception ); } catch ( EndOfStreamException exception ) { throw new BadIndexesException( "Unexpected end of table. Table or indexes are corrupted.", exception ); } catch ( StringCorruptedException exception ) { throw new BadIndexesException( "Unexpected end of table. Table or indexes are corrupted.", exception ); } } } private bool ReadToNearestEndMarker( long columnOffset ) { try { if ( _file.Length <= columnOffset + 4 ) { return false; } _file.Position = columnOffset + 4; for ( ;; ) { byte readByte = (byte)_file.ReadByte(); if ( readByte == 0xDE ) { if ( _file.Position >= 4 ) { _file.Position -= 4; if ( _reader.ReadUInt32() == StringColumn.END_MARKER ) { return true; } } } } } catch ( EndOfStreamException ) { return false; } } private void TryToFixEndMarker( long startRecordOffset, long startColumnOffset ) { if ( _file.Length <= startColumnOffset + 4 ) { return; } _file.Position = startColumnOffset + 4; try { for ( ;; ) { byte readByte = (byte)_file.ReadByte(); if ( readByte == 0xDE ) { if ( _file.Position >= 4 ) { _file.Position -= 4; if ( _reader.ReadUInt32() == StringColumn.END_MARKER ) { long endOffset = _file.Position; _file.Position = startColumnOffset; int stringLength = (int)( endOffset - startColumnOffset - 8 ); _writer.Write( stringLength ); _file.Position = startRecordOffset; _file.ReadByte(); LoadRecordToColumns(); return; } } } } } catch ( EndOfStreamException ) { Console.WriteLine( "Table was truncated because last records are corrupted" ); _file.SetLength( startRecordOffset ); } } /* * Method for reading of records when rebuild and defragment db. * */ private bool LoadRecordToColumns( long startRecordOffset, bool fix ) { int columnsCount = _columns.Length; long columnOffset = 0; for ( int i = 0; i < columnsCount; ++i ) { try { columnOffset = _file.Position; _columns[i].LoadValue( _reader ); } catch ( DateTimeCorruptedException ) { if ( fix ) { _file.Position -= 8; _columns[i].SaveValue( _writer ); } } catch ( StringCorruptedException exception ) { _tracer.TraceException( exception ); if ( fix ) { TryToFixEndMarker( startRecordOffset, columnOffset ); } else { if ( ReadToNearestEndMarker( columnOffset ) ) { i++; for ( ;i < columnsCount; ++i ) { _columns[i].LoadValue( _reader ); } } } return false; } catch ( NoEndMarkerException noEndMarker ) { if ( !fix ) { throw new BadIndexesException( "Table or indexes are corrupted: String corrupted no end marker in column = " + _columns[i].Name + " fix = " + fix, noEndMarker ); } long currentOffset = _file.Position; try { _file.ReadByte(); try { LoadRecordToColumns(); } catch ( Exception ) { TryToFixEndMarker( startRecordOffset, columnOffset ); return false; } _file.Position = currentOffset - 4; _writer.Write( StringColumn.END_MARKER ); i++; for ( ;i < columnsCount; ++i ) { _columns[i].LoadValue( _reader ); } _file.Position = startRecordOffset; _file.ReadByte(); LoadRecordToColumns(); return false; } catch ( Exception ) { _file.Position = currentOffset; throw noEndMarker; } } catch ( EndOfStreamException exception ) { _tracer.Trace( "Unexpected end of table. Table or indexes are corrupted." ); _tracer.TraceException( exception ); throw exception; } } return true; } private void CheckIfOffsetValid( int offset ) { CheckIfSingleOffsetValid( offset, false ); } private void CheckIfSingleOffsetValid( int offset, bool minusOneIsExpected ) { if ( minusOneIsExpected && offset == -1 ) { return; } if ( _fixedSize && offset != -1 ) { if ( offset % _recordSize != 0 ) { throw new BadIndexesException( "An attempt was made to read from bad offset: " + offset + " When recordSize is " + _recordSize, null ); } } } private void CheckIfOffsetValid( int offset, bool forReading ) { if ( forReading ) { if ( offset < 0 || _file.Length <= offset ) { throw new BadIndexesException( "An attempt was made to move the file pointer before the beginning of the file: offset = " + offset, null ); } } else { if ( offset < 0 || _file.Length < offset ) { throw new BadIndexesException( "An attempt was made to move the file pointer before the beginning of the file: offset = " + offset, null ); } } CheckIfSingleOffsetValid( offset, !forReading ); } public object[] ReadRecordFromOffset( int offset ) { CheckIfOffsetValid( offset, true ); _file.Position = offset; RecordType recType = (RecordType)_file.ReadByte(); if ( recType != RecordType.NormalMarker ) { throw new BadIndexesException( "Index points to not 'Normal' record. RecordType was = " + recType + " by offset = " + offset, null ); } LoadRecordToColumns(); _loadedRecords++; _loadedRecordSize += _file.Position - offset; return _fields; } public int NextID() { lock( this ) { return _tblStructure.NextID(); } } public int PeekNextID() { lock( this ) { return _tblStructure.PeekNextID(); } } public IRecord NewRecord( ) { Record record = new Record( this, -1 ); if ( _idColumn != null ) { int id = NextID(); _idColumn.Value = id; record.SetValue( 0, IntInternalizer.Intern( id ) ); } return record; } public void DeleteRecord( int offset, object[] oldFields ) { CheckIfOffsetValid( offset, false ); _file.Position = offset; RecordType recType = (RecordType)_file.ReadByte(); if ( recType != RecordType.NormalMarker ) { throw new BadIndexesException( "Attemption to delete wrong record.", null ); } RemoveValuesFromIndex( offset, oldFields, null ); _tblStructure.Dirty = true; _file.Position = offset; _writer.Write( (byte)RecordType.DeletedMarker ); if ( _autoFlush ) { _writer.Flush(); } } private void RemoveValuesFromIndex( int offset, object[] oldFields, object[] newFields ) { bool fieldsChanged = false; if ( _canUpdate && newFields != null ) { for ( int i = 0; i < _fieldChangeFlag.Length; i++ ) { bool changed = !oldFields[i].Equals( newFields[i] ); if ( changed ) { fieldsChanged = true; } _fieldChangeFlag[i] = changed; } if ( !fieldsChanged ) return; } for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) { object oldValue = oldFields[i]; bool bRemoveFromIndex = true; if ( _canUpdate && newFields != null ) { bRemoveFromIndex = _fieldChangeFlag[i]; } else if ( !_canUpdate ) { string strValue = oldValue as string; if ( strValue != null ) { oldValue = DBHelper.GetHashCodeInLowerCase( strValue ); } } if ( bRemoveFromIndex ) { dbIndex.RemoveIndexEntry( (IComparable)oldValue, offset ); } } } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; object oldValue1 = oldFields[compoundIndex._columnIndex1]; object oldValue2 = oldFields[ compoundIndex._columnIndex2 ]; bool bRemoveFromIndex = true; if ( _canUpdate && newFields != null ) { bRemoveFromIndex = _fieldChangeFlag[compoundIndex._columnIndex1]; if ( !bRemoveFromIndex ) { bRemoveFromIndex = _fieldChangeFlag[compoundIndex._columnIndex2]; } } else if ( !_canUpdate ) { string strValue1 = oldValue1 as string; if ( strValue1 != null ) { oldValue1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = oldValue2 as string; if ( strValue2 != null ) { oldValue2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } } if ( bRemoveFromIndex ) { SetCompoundKey( _compound, oldValue1, oldValue2 ); dbIndex.RemoveIndexEntry( _compound, offset ); } } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndexWithValue compoundIndexWithValue = (CompoundIndexWithValue)_compoundIndexesWithValue[i]; if ( compoundIndexWithValue != null ) { IDBIndex dbIndex = compoundIndexWithValue._dbIndex; object oldValue1 = oldFields[compoundIndexWithValue._columnIndex1]; object oldValue2 = oldFields[compoundIndexWithValue._columnIndex2]; object oldValue3 = oldFields[compoundIndexWithValue._fieldIndex]; bool bRemoveFromIndex = true; if ( _canUpdate && newFields != null ) { bRemoveFromIndex = _fieldChangeFlag[compoundIndexWithValue._columnIndex1]; if ( !bRemoveFromIndex ) { bRemoveFromIndex = _fieldChangeFlag[compoundIndexWithValue._columnIndex2]; } if ( !bRemoveFromIndex ) { bRemoveFromIndex = _fieldChangeFlag[compoundIndexWithValue._fieldIndex]; } } else if ( !_canUpdate ) { string strValue1 = oldValue1 as string; if ( strValue1 != null ) { oldValue1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = oldValue2 as string; if ( strValue2 != null ) { oldValue2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } } if ( bRemoveFromIndex ) { SetCompoundKey( _compoundAndValue, oldValue1, oldValue2, oldValue3 ); dbIndex.RemoveIndexEntry( _compoundAndValue, offset ); } } } } #endregion #region Constructing table ( columns, indexes ) private void OpenStreams() { _fileName = DBHelper.GetFullNameForTable( _database.Path, _database.Name, Name ); _file = DBHelper.PrepareIOFile( this, _fileName, FileMode.OpenOrCreate ); // BinaryWriter has UTF-8 as the default encoding, but it sets // throwOnInvalidBytes to true, which we don't want (we can receive incorrect // surrogate pairs from high upstream, and there is nothing we can do about that // on the DB level) // see, for example, #4742 UTF8Encoding encoding = new UTF8Encoding(); _reader = new SafeBinaryReader( _file, encoding ); _writer = new SafeBinaryWriter( _file, encoding ); } private int _recordSize = 1; public int AddColumn( Column column ) { if ( column.Name == "Id" ) { _idColumn = column; } if ( column.Type == ColumnType.BLOB ) { _isRecordWithBLOB = true; } if ( column.Type == ColumnType.String || _isRecordWithBLOB ) { _canUpdate = false; _fixedSize = false; } if ( column.GetFixedFactory() != null ) { _recordSize += column.GetFixedFactory().KeySize; } int columnIndex = _columns.Length; Column[] newColumns = new Column[ columnIndex + 1 ]; object[] newFields = new object[ columnIndex + 1 ]; _fieldChangeFlag = new bool[columnIndex + 1]; for ( int i = 0; i < columnIndex; ++i ) { newColumns[i] = _columns[i]; newFields[i] = _fields[i]; } newColumns[columnIndex] = column; newFields[columnIndex] = null; _columns = newColumns; _fields = newFields; for ( int i = 0; i < _columns.Length; ++i ) { _columns[i].SetSharedFields( _fields, i ); _fieldChangeFlag[i] = false; } _indexes.Add( null ); _compoundIndexes.Add( null ); _compoundIndexesWithValue.Add( null ); //_fieldChangeFlag.Add( false ); _columnsMap[column.Name] = columnIndex; return columnIndex; } internal void DropIndex( string columnName ) { _tracer.Trace( "Drop index: " + columnName ); int columnIndex = GetColumnIndexByName( columnName ); IDBIndex dbIndex = _indexes[columnIndex] as IDBIndex; if ( dbIndex != null ) { _indexes[columnIndex] = null; dbIndex.Close(); return; } throw new IndexDoesNotExistException( columnName ); } internal void DropCompoundIndex( string indexName ) { string[] names = indexName.Split( '#' ); if ( names.Length == 2 ) { int firstIndex = GetColumnIndexByName( names[0] ); int secondIndex = GetColumnIndexByName( names[1] ); CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[firstIndex]; compoundIndex._dbIndex.Close(); _indexesCompoundMap.Remove( (firstIndex<< 6)+secondIndex ); _compoundIndexes[firstIndex] = null; } CheckCountForIndexes(); } internal void DropCompoundIndexWithValue( string indexName ) { string[] names = indexName.Split( '#' ); if ( names.Length == 2 ) { int firstIndex = GetColumnIndexByName( names[0] ); int secondIndex = GetColumnIndexByName( names[1] ); CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexesWithValue[firstIndex]; compoundIndex._dbIndex.Close(); _indexesCompoundMapWithValue.Remove( (firstIndex<< 6)+secondIndex ); _compoundIndexesWithValue[firstIndex] = null; } CheckCountForIndexes(); } public void AddIndex( string indexName, IDBIndex dbIndex ) { Column column = GetColumnByName( indexName ); if ( column == null ) { throw new ColumnDoesNotExistException( "Column does not exist", indexName ); } _indexes.Insert( GetColumnIndexByName( indexName ), dbIndex ); column.SetIndex( dbIndex, GetColumnIndexByName( indexName ) ); } public void AddCompoundIndex( string indexName, IDBIndex dbIndex ) { string[] names = indexName.Split( '#' ); if ( names.Length == 2 ) { int firstIndex = GetColumnIndexByName( names[0] ); int secondIndex = GetColumnIndexByName( names[1] ); _indexesCompoundMap[(firstIndex<< 6)+secondIndex] = dbIndex; _compoundIndexes.Insert( GetColumnIndexByName( dbIndex.FirstCompoundName ), new CompoundIndex( dbIndex, GetColumnByName(names[0]), firstIndex, GetColumnByName(names[1]), secondIndex ) ); } } public void AddCompoundIndexWithValue( string indexName, IDBIndex dbIndex, string columnName ) { string[] names = indexName.Split( '#' ); if ( names.Length == 2 ) { int firstIndex = GetColumnIndexByName( names[0] ); int secondIndex = GetColumnIndexByName( names[1] ); CompoundIndexWithValue compoundIndexWithValue = new CompoundIndexWithValue( dbIndex, GetColumnByName(names[0]), firstIndex, GetColumnByName(names[1]), secondIndex, GetColumnByName(columnName), GetColumnIndexByName(columnName)); _indexesCompoundMapWithValue[(firstIndex<< 6)+secondIndex] = compoundIndexWithValue; _compoundIndexesWithValue.Insert( firstIndex, compoundIndexWithValue ); } } public ArrayList GetColumnInfos() { int columnsCount = _columns.Length; ArrayList columnInfos = new ArrayList( columnsCount ); for( int i = 0; i < columnsCount; i++ ) { Column column = _columns[i]; columnInfos.Add( new ColumnInfo( column.Name, column.Type ) ); } return columnInfos; } public Column GetColumn( string name ) { Column column = GetColumnByName(name); if ( column != null ) { return column; } throw new ColumnDoesNotExistException( "Column does not exist", name ); } #endregion #region loading and shutting down for table private void ShutdownIndexes( ) { for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) dbIndex.Shutdown(); } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null ) { compoundIndex._dbIndex.Shutdown(); } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexesWithValue[i] as CompoundIndex; if ( compoundIndex != null ) { compoundIndex._dbIndex.Shutdown(); } } } private void FlushIndexes( ) { for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) dbIndex.Flush(); } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null ) { compoundIndex._dbIndex.Flush(); } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexesWithValue[i] as CompoundIndex; if ( compoundIndex != null ) { compoundIndex._dbIndex.Flush(); } } } public void Flush() { Monitor.Enter( this ); _tblStructure.SetTotalCount( _totalCount ); FlushIndexes( ); _tblStructure.Dirty = false; Monitor.Exit( this ); } private void TableShutdown() { _writer.Close(); _file.Close(); } internal void Shutdown() { lock ( this ) { Flush(); _writer.Flush(); TableShutdown(); ShutdownIndexes( ); } } #endregion #region Creating ResultSets public IRecord GetRecordByEqual( int columnIndex, object key ) { if ( key == null ) return null; IResultSet resultSet = null; IRecord record = null; try { resultSet = CreateResultSet( columnIndex, key, true ); IEnumerator enumerator = resultSet.GetEnumerator(); try { if ( enumerator.MoveNext() ) { record = (IRecord) enumerator.Current; } } finally { IDisposable disposable = enumerator as IDisposable; if ( disposable != null ) { disposable.Dispose(); } } } finally { if ( resultSet != null ) resultSet.Dispose(); } return record; } private void SetCompoundKey( Compound compound, object key1, object key2 ) { compound._key1 = key1 as IComparable; compound._key2 = key2 as IComparable; } private void SetCompoundKey( CompoundAndValue compoundAndValue, object key1, object key2, object valueKey ) { compoundAndValue._key1 = key1 as IComparable; compoundAndValue._key2 = key2 as IComparable; compoundAndValue._value = valueKey as IComparable; } public ICountedResultSet EmptyResultSet { get { return _emptyResultSet; } } private ArrayList _keys_offsets = new ArrayList(); void PrepareKeysOffset() { _keys_offsets = new ArrayList(); } private ArrayList GetKeysOffset() { return _keys_offsets; } public ICountedResultSet CreateResultSet( int columnIndex1, object key1, int columnIndex2, object key2, bool readOnly ) { int stringColumnIndex = -1; string stringParam = null; if ( !_canUpdate ) { string strKey1 = key1 as string; if ( strKey1 != null ) { stringParam = strKey1; key1 = DBHelper.GetHashCodeInLowerCase( strKey1 ); stringColumnIndex = columnIndex1; } string strKey2 = key2 as string; if ( strKey2 != null ) { stringParam = strKey2; key2 = DBHelper.GetHashCodeInLowerCase( strKey2 ); stringColumnIndex = columnIndex2; } } Monitor.Enter( this ); try { object compoundIndexWithValue = _indexesCompoundMapWithValue[(columnIndex1 << 6)+columnIndex2]; if ( compoundIndexWithValue != null ) { CompoundIndexWithValue compoundIndexWV = compoundIndexWithValue as CompoundIndexWithValue; IDBIndex dbIndex = compoundIndexWV._dbIndex; FixedLengthKey valueKey = dbIndex.SearchKeyValue(); SetCompoundKey( _compoundAndValue, key1, key2, valueKey.MinKey ); SetCompoundKey( _compoundAndValueSecond, key1, key2, valueKey.MaxKey ); IEnumerable enumerable; if( readOnly ) { enumerable = dbIndex.SearchForRange( _compoundAndValue, _compoundAndValueSecond ); } else { ArrayList keys_offsets = GetKeysOffset(); dbIndex.SearchForRange( keys_offsets, _compoundAndValue, _compoundAndValueSecond ); if( keys_offsets.Count == 0 ) { Monitor.Exit( this ); return _emptyResultSet; } PrepareKeysOffset(); enumerable = keys_offsets; } ICountedResultSet retResultSet = new FromIndexWithValueResultSet( this, enumerable, compoundIndexWV._columnIndex1, compoundIndexWV._columnIndex2, compoundIndexWV._fieldIndex ); if ( stringColumnIndex > -1 ) { retResultSet = new PreLoadedResultSet( retResultSet, stringColumnIndex, stringParam ); } return retResultSet; } object compoundIndex = _indexesCompoundMap[(columnIndex1 << 6)+columnIndex2]; if ( compoundIndex != null ) { IDBIndex dbIndex = (IDBIndex)compoundIndex; SetCompoundKey( _compound, key1, key2 ); ICountedResultSet retResultSet; if( readOnly ) { retResultSet = new EnumerableResultSet( this, dbIndex.SearchForRange( _compound, _compound ) ); } else { IntArrayList offsets = IntArrayListPool.Alloc(); dbIndex.SearchForRange( offsets, _compound, _compound ); retResultSet = new ResultSet( this, offsets ); } if ( stringColumnIndex > -1 ) { retResultSet = new PreLoadedResultSet( retResultSet, stringColumnIndex, stringParam ); } return retResultSet; } throw new Exception( "No index" ); } catch ( Exception exception ) { Tracer._TraceException( exception ); Monitor.Exit( this ); throw; } } public ICountedResultSet CreateModifiableResultSet( int columnIndex, object key ) { return CreateResultSet( columnIndex, key, false ); } public IResultSet CreateResultSet( int columnIndex, object key ) { return CreateResultSet( columnIndex, key, true ); } private ICountedResultSet CreateResultSet( int columnIndex, object key, bool readOnly ) { int stringColumnhIndex = -1; string stringParam = null; if ( key == null ) { return _emptyResultSet; } if ( !_canUpdate ) { string strKey = key as string; if ( strKey != null ) { stringParam = strKey; key = DBHelper.GetHashCodeInLowerCase( strKey ); stringColumnhIndex = columnIndex; } } if ( columnIndex < _columns.Length ) { Monitor.Enter( this ); ICountedResultSet retResultSet = PrepareResultSet( columnIndex, key, readOnly ); if ( stringColumnhIndex > -1 ) { retResultSet = new PreLoadedResultSet( retResultSet, stringColumnhIndex, stringParam ); } return retResultSet; } throw new ColumnDoesNotExistException( "Column does not exist", columnIndex.ToString() ); } public void GetAllOffsets( IntArrayList offsets ) { Monitor.Enter( this ); try { for ( int i = 0; i < _indexes.Count; ++i ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) { dbIndex.GetAllOffsets( offsets ); return; } } for ( int i = 0; i < _compoundIndexes.Count; ++i ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { compoundIndex._dbIndex.GetAllOffsets( offsets ); return; } } for ( int i = 0; i < _compoundIndexesWithValue.Count; ++i ) { CompoundIndexWithValue compoundIndex = (CompoundIndexWithValue)_compoundIndexes[i]; if ( compoundIndex != null ) { compoundIndex._dbIndex.GetAllOffsets( offsets ); return; } } } catch ( Exception exception ) { _tracer.TraceException( exception ); Monitor.Exit( this ); } } public ICountedResultSet CreateResultSet( int columnIndex ) { Monitor.Enter( this ); try { bool isCompound; CompoundIndexWithValue withValue; IDBIndex dbIndex = GetIndex( columnIndex, out isCompound, out withValue ); if( dbIndex != null ) { if( withValue == null ) { IntArrayList offsets = IntArrayListPool.Alloc(); dbIndex.GetAllOffsets( offsets ); if ( offsets.Count == 0 ) { IntArrayListPool.Dispose( offsets ); Monitor.Exit( this ); return _emptyResultSet; } return new ResultSet( this, offsets ); } else { ArrayList keys_offsets = GetKeysOffset(); dbIndex.GetAllOffsets( keys_offsets ); PrepareKeysOffset(); return new FromIndexWithValueResultSet( this, keys_offsets, withValue._columnIndex1, withValue._columnIndex2, withValue._fieldIndex ); } } } catch ( Exception exception ) { _tracer.TraceException( exception ); Monitor.Exit( this ); } throw new ColumnHasNoIndexException( "Column '" + columnIndex.ToString() + "' has not index" );//no appropriate index } public ICountedResultSet CreateResultSetForRange( int firstColumnIndex, object firstKey, int secondColumnIndex, object beginKey, object endKey ) { int stringColumnIndex = -1; string stringParam = null; if ( !_canUpdate ) { string strFirstKey = firstKey as string; if ( strFirstKey != null ) { stringParam = strFirstKey; firstKey = DBHelper.GetHashCodeInLowerCase( strFirstKey ); stringColumnIndex = firstColumnIndex; } } Monitor.Enter( this ); try { object objCompoundIndexWithValue = _indexesCompoundMapWithValue[(firstColumnIndex << 6)+secondColumnIndex]; if ( objCompoundIndexWithValue != null ) { CompoundIndexWithValue compoundIndexWV = (CompoundIndexWithValue)objCompoundIndexWithValue; IDBIndex compoundIndex = compoundIndexWV._dbIndex; FixedLengthKey valueKey = compoundIndex.SearchKeyValue(); SetCompoundKey( _compoundAndValue, firstKey, beginKey, valueKey.MinKey ); SetCompoundKey( _compoundAndValueSecond, firstKey, endKey, valueKey.MaxKey ); ICountedResultSet retResultSet = new FromIndexWithValueResultSet( this, compoundIndex.SearchForRange( _compoundAndValue, _compoundAndValueSecond ), compoundIndexWV._columnIndex1, compoundIndexWV._columnIndex2, compoundIndexWV._fieldIndex ); if ( stringColumnIndex > -1 ) { retResultSet = new PreLoadedResultSet( retResultSet, stringColumnIndex, stringParam ); } return retResultSet; } object objCompoundIndex = _indexesCompoundMap[(firstColumnIndex << 6)+secondColumnIndex]; if ( objCompoundIndex != null ) { IDBIndex compoundIndex = objCompoundIndex as IDBIndex; SetCompoundKey( _compound, firstKey, beginKey ); SetCompoundKey( _compoundSecond, firstKey, endKey ); ICountedResultSet retResultSet = new EnumerableResultSet( this, compoundIndex.SearchForRange( _compound, _compoundSecond ) ); if ( stringColumnIndex > -1 ) { retResultSet = new PreLoadedResultSet( retResultSet, stringColumnIndex, stringParam ); } return retResultSet; } throw new Exception( "No appropriate index" ); } catch ( Exception exception ) { Tracer._TraceException( exception ); Monitor.Exit( this ); throw; } } private ICountedResultSet PrepareResultSet( int fieldIndex, object key, bool readOnly ) { bool isCompound; CompoundIndexWithValue withValue; IDBIndex index = GetIndex( fieldIndex, out isCompound, out withValue ); if ( withValue == null ) { if ( readOnly ) { if ( isCompound ) { FixedLengthKey sKey = index.SearchKeySecond(); SetCompoundKey( _compound, key, sKey.MinKey ); SetCompoundKey( _compoundSecond, key, sKey.MaxKey ); return new EnumerableResultSet( this, index.SearchForRange( _compound, _compoundSecond ) ); } else { IComparable comparableKey = key as IComparable; return new EnumerableResultSet( this, index.SearchForRange( comparableKey, comparableKey ) ); } } IntArrayList offsets = IntArrayListPool.Alloc(); if ( isCompound ) { FixedLengthKey sKey = index.SearchKeySecond(); SetCompoundKey( _compound, key, sKey.MinKey ); SetCompoundKey( _compoundSecond, key, sKey.MaxKey ); index.SearchForRange( offsets, _compound, _compoundSecond ); } else { index.SearchForRange( offsets, key as IComparable, key as IComparable ); } if ( offsets.Count == 0 ) { IntArrayListPool.Dispose( offsets ); Monitor.Exit( this ); return _emptyResultSet; } return new ResultSet( this, offsets ); } else { FixedLengthKey sKey = index.SearchKeySecond(); FixedLengthKey valueKey = index.SearchKeyValue(); SetCompoundKey( _compoundAndValue, key, sKey.MinKey, valueKey.MinKey ); SetCompoundKey( _compoundAndValueSecond, key, sKey.MaxKey, valueKey.MaxKey ); IEnumerable enumerable; if( readOnly ) { enumerable = index.SearchForRange( _compoundAndValue, _compoundAndValueSecond ); } else { ArrayList keys_offsets = GetKeysOffset(); index.SearchForRange( keys_offsets, _compoundAndValue, _compoundAndValueSecond ); if( keys_offsets.Count == 0 ) { Monitor.Exit( this ); return _emptyResultSet; } PrepareKeysOffset(); enumerable = keys_offsets; } return new FromIndexWithValueResultSet( this, enumerable, withValue._columnIndex1, withValue._columnIndex2, withValue._fieldIndex ); } } #endregion #region Working with BLOBs public IBLOB CreateBLOB( Stream stream ) { return new BLOB( this, stream ); } #endregion #region Getters public int GetID() { if ( _idColumn == null ) return 0; return (int)_idColumn.Value; } private Column GetColumnByName( string coloumnName ) { object obj = _columnsMap[coloumnName]; if ( obj == null ) return null; int columnIndex = (int)obj; return _columns[columnIndex]; } public int GetColumnIndexByName( string columnName ) { return (int)_columnsMap[columnName]; } public bool IsRecordWithBLOB { get { return _isRecordWithBLOB; } } public DatabaseMode Mode { get { return _tblStructure.Mode; } } public int Version { get { return _database.Version; } } public int Count { get { for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) { return dbIndex.Count; } } for ( int i = 0; i < _compoundIndexes.Count;i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null ) { return compoundIndex._dbIndex.Count; } } for ( int i = 0; i < _compoundIndexesWithValue.Count;i++ ) { CompoundIndex compoundIndex = _compoundIndexesWithValue[i] as CompoundIndex; if ( compoundIndex != null ) { return compoundIndex._dbIndex.Count; } } throw new Exception( "Cannot access count for tables with no indexes" ); } } public string Name { get { return _tblStructure.Name; } } public IDatabaseDesign Database { get{ return _database; } } public bool Dirty { get { return _tblStructure.Dirty; } } public IDBIndex GetIndex( int indexNum, out bool isCompound, out CompoundIndexWithValue withValue ) { IDBIndex dbIndex; withValue = null; dbIndex = _indexes[indexNum] as IDBIndex; if ( dbIndex != null ) { isCompound = false; return dbIndex; } withValue = _compoundIndexesWithValue[indexNum] as CompoundIndexWithValue; if ( withValue != null ) { dbIndex = withValue._dbIndex; if ( dbIndex != null ) { isCompound = true; return dbIndex; } } CompoundIndex compoundIndex = _compoundIndexes[indexNum] as CompoundIndex; if ( compoundIndex != null ) { isCompound = true; return compoundIndex._dbIndex; } throw new ColumnHasNoIndexException( "Column '" + indexNum.ToString() + "' has not index" );//no appropriate index } #endregion #region Helpers ( rebuilding, defragmentation, dump ) public int SortedColumn { get { return _sortedColumn; } set { _sortedColumn = value; } } private void DumpCounts() { Console.WriteLine( "#############################################" ); _tracer.Trace( "#############################################" ); Console.WriteLine( "Common count: " + Count ); _tracer.Trace( "Common count: " + Count ); for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = (IDBIndex)_indexes[i]; if ( dbIndex != null ) { Console.WriteLine( dbIndex.Name + " index count: " + dbIndex.Count ); _tracer.Trace( dbIndex.Name + " index count: " + dbIndex.Count ); } } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; Console.WriteLine( dbIndex.Name + " index count: " + dbIndex.Count ); _tracer.Trace( dbIndex.Name + " index count: " + dbIndex.Count ); } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexesWithValue[i]; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; Console.WriteLine( dbIndex.Name + " index count: " + dbIndex.Count ); _tracer.Trace( dbIndex.Name + " index count: " + dbIndex.Count ); } } Console.WriteLine( "#############################################" ); _tracer.Trace( "#############################################" ); } private void CheckCountForIndexes() { int count = 0; for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = _indexes[i] as IDBIndex; if ( dbIndex != null ) { if( count == 0 ) { count = dbIndex.Count; } else if( dbIndex.Count != count ) { DumpCounts(); throw new BadIndexesException( "Table: " + Name, null ); } } } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null) { if( count == 0 ) { count = compoundIndex._dbIndex.Count; } else if( compoundIndex._dbIndex.Count != count ) { DumpCounts(); throw new BadIndexesException( "Table: " + Name, null ); } } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexesWithValue[i] as CompoundIndex; if ( compoundIndex != null) { if( count == 0 ) { count = compoundIndex._dbIndex.Count; } else if( compoundIndex._dbIndex.Count != count ) { DumpCounts(); throw new BadIndexesException( "Table: " + Name, null ); } } } if( !Dirty && count == 0 && !IsEmpty() ) { throw new BadIndexesException( "Table: " + Name, null ); } } public void RebuildIndexes() { TableRebuilder.RebuildIndexes( this, false ); } public void RebuildIndexes( bool resetNextId ) { TableRebuilder.RebuildIndexes( this, resetNextId ); } public void OpenIndexes() { for ( int i = 0; i < _indexes.Count; ++i ) { IDBIndex dbIndex = (IDBIndex)_indexes[i]; if ( dbIndex != null ) { if( !dbIndex.Open() ) { if ( !IsEmpty() && Count == 0 ) { throw new BadIndexesException(); } } } } for ( int i = 0; i < _compoundIndexes.Count; ++i ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { if( !compoundIndex._dbIndex.Open() ) { if ( !IsEmpty() && Count == 0 ) { throw new BadIndexesException(); } } } } for ( int i = 0; i < _compoundIndexesWithValue.Count; ++i ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexesWithValue[i]; if ( compoundIndex != null ) { if( !compoundIndex._dbIndex.Open() ) { if ( !IsEmpty() && Count == 0 ) { throw new BadIndexesException(); } } } } CheckCountForIndexes(); } private void EndOfUpgrade() { for ( int i = 0; i < _columns.Length; ++i ) { StringColumnTo22 stringColumn = _columns[i] as StringColumnTo22; if ( stringColumn != null ) { _columns[i] = stringColumn.AsStringColumn(); } } CheckedNeedUpgradeTo22(); } public void Defragment( ) { if ( NeedUpgradeTo22 ) { for ( int i = 0; i < _columns.Length; ++i ) { StringColumnTo22 stringColumn = _columns[i] as StringColumnTo22; if ( stringColumn != null ) { if ( ReadToNearestEndMarker( 0 ) ) { EndOfUpgrade(); } } } } TableDefragmentator.Defragment( this ); if ( NeedUpgradeTo22 ) { EndOfUpgrade(); } } public void DefragmentIndexes( bool idleMode ) { ArrayList indexes = new ArrayList(); lock( this ) { for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = (IDBIndex)_indexes[i]; if ( dbIndex != null ) { indexes.Add( dbIndex ); } } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { indexes.Add( compoundIndex._dbIndex ); } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexesWithValue[i]; if ( compoundIndex != null ) { indexes.Add( compoundIndex._dbIndex ); } } foreach( IDBIndex dbIndex in indexes ) { if( idleMode && !Core.IsSystemIdle ) { break; } dbIndex.Defragment( idleMode ); } } } private void ClearIndexes() { for ( int i = 0; i < _indexes.Count; i++ ) { IDBIndex dbIndex = (IDBIndex)_indexes[i]; if ( dbIndex != null ) { dbIndex.Clear(); } } for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexes[i]; if ( compoundIndex != null ) { compoundIndex._dbIndex.Clear(); } } for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndex compoundIndex = (CompoundIndex)_compoundIndexesWithValue[i]; if ( compoundIndex != null ) { compoundIndex._dbIndex.Clear(); } } } public bool IsEmpty() { return _file.Length == 0; } public abstract class RecordEnum { private Table _table; public RecordEnum( Table table ) { _table = table; } protected abstract void OnRecordFetched( RecordType recType, long size ); public virtual void EnumerateRecords( bool fix ) { Stream stream = _table._file; stream.Position = 0; long offset = 0; try { for ( ;; ) { RecordType recType = _table.ReadRecord( fix ); long curOffset = stream.Position; OnRecordFetched( recType, curOffset - offset ); offset = curOffset; } } catch ( EndOfStreamException ) { } } } public class RecordCounter : RecordEnum { private int _normal; private int _total; public RecordCounter( Table table ) : base( table ) { } public static RecordsCounts GetRecordsCounts( Table table ) { lock( table ) { RecordCounter counter = new RecordCounter( table ); counter.EnumerateRecords( false ); return counter.RecordsCounts; } } protected override void OnRecordFetched( RecordType recType, long size ) { _total++; if ( recType == RecordType.NormalMarker ) { _normal++; } } public RecordsCounts RecordsCounts{ get { return new RecordsCounts( _normal, _total ); } } } public class TableRebuilder : RecordEnum { protected Table _table; protected Column[] _columns; private int _nextID = 0; private int _totalCount = 0; private int _currentOffset = 0; private long _fileLength; private int _lastTickCount; public TableRebuilder( Table table ) : base( table ) { _table = table; _fileLength = _table._file.Length; _columns = _table._columns; _nextID = _table._tblStructure.NextID(); Trace.WriteLine( "Rebuild indexes for: " + _table.Name ); } public virtual void Finish() { _table._tblStructure.SetNextID( _nextID ); _table._totalCount = _totalCount; _table._tblStructure.SetTotalCount( _totalCount ); } public static void RebuildIndexes( Table table, bool resetNextId ) { lock( table ) { TableRebuilder rebuilder = new TableRebuilder( table ); if ( resetNextId ) { rebuilder._nextID = 0; } rebuilder.EnumerateRecords( true ); rebuilder.Finish(); } } public override void EnumerateRecords( bool fix ) { int multiplier = DBIndex._cacheSizeMultiplier; DBIndex._cacheSizeMultiplier = 20; try { _table.ClearIndexes(); base.EnumerateRecords( fix ); } finally { DBIndex._cacheSizeMultiplier = multiplier; } } protected virtual int OffsetToWrite { get { return _currentOffset; } } protected override void OnRecordFetched( RecordType recType, long size ) { _totalCount++; int currentTickCount = Environment.TickCount; if ( ( _table.NeedUpgradeTo22 && recType == RecordType.Normal ) || recType == RecordType.NormalMarker ) { for ( int i = 0; i < _columns.Length; ++i ) { Column column = _columns[i]; if ( column.IndexNum != -1 ) { object value = column.Value; string strValue = value as string; if ( strValue != null ) { value = DBHelper.GetHashCodeInLowerCase( strValue ); } column.Index.AddIndexEntry( (IComparable)value, OffsetToWrite ); } } _table.AddEntryToCompoundIndexes( OffsetToWrite ); _table.AddEntryToCompoundIndexesWithValue( OffsetToWrite ); int id = _table.GetID(); if ( id+1 > _nextID ) { _nextID = id+1; } } _currentOffset += (int)size; if ( currentTickCount - _lastTickCount > 300 ) { if ( _fileLength != 0 ) { _table._database.OnProgress( "Rebuilding indexes for '" + _table.Name + "' table...", OffsetToWrite, (int)_fileLength ); } _lastTickCount = currentTickCount; } } } public class TableDefragmentator : TableRebuilder { private SafeBinaryWriter _writerDefragment = null; private string _strFullPath = null; private CachedStream _fileDefragment = null; private int _totalCount = 0; public TableDefragmentator( Table table ) : base( table ) { _strFullPath = DBHelper.GetFullNameForTable( _table._database.Path, _table._database.Name, _table.Name + "_defragment" ); _fileDefragment = DBHelper.PrepareIOFile( table, _strFullPath, FileMode.Create ); Encoding encoding = new UTF8Encoding(); // throwOnInvalidBytes=false: see OM-7096 _writerDefragment = new SafeBinaryWriter( _fileDefragment, encoding ); } public override void Finish() { _table.TableShutdown(); Rollback( _table._fileName ); _table.OpenStreams(); _table._totalCount = _totalCount; _table._tblStructure.SetTotalCount( _totalCount ); } public void Rollback( string fileName ) { CleanUp(); try { File.Copy( _strFullPath, fileName, true ); } catch { File.Delete( fileName ); File.Move( _strFullPath, fileName ); return; } File.Delete( _strFullPath ); } public void CleanUp() { if ( _fileDefragment != null ) _fileDefragment.Close(); if ( _writerDefragment != null ) _writerDefragment.Close(); _fileDefragment = null; _writerDefragment = null; } public override void EnumerateRecords( bool fix ) { if( _table.NeedUpgradeTo22 || _table.SortedColumn < 0 ) { try { if ( _table.NeedUpgradeTo22 ) { Tracer._Trace( "Need Upgrade To Version 22, TABLE COUNT = " + _table.Count ); bool processByOffsets = false; if ( _table.Count != 0 ) { ArrayList columns = _table.GetColumnInfos(); foreach ( ColumnInfo column in columns ) { if ( column.Type == ColumnType.String ) { Tracer._Trace( "There is String column '" + column.Name + "'" ); processByOffsets = true; break; } } } if ( processByOffsets ) { IntArrayList offsets = new IntArrayList(); _table.GetAllOffsets( offsets ); if( _table.Count == offsets.Count ) { try { Tracer._Trace( "Try common enumeration" ); base.EnumerateRecords( fix ); return; } catch ( StringCorruptedException exception ) { Tracer._TraceException( exception ); } catch ( BadIndexesException exception ) { Tracer._TraceException( exception ); } Tracer._Trace( "Try enumeration with offsets from index" ); _table.ClearIndexes(); offsets.Sort(); for( int i = 0; i < offsets.Count; ++i ) { _table.ReadRecordFromOffsetNoChecking( offsets[ i ] ); OnRecordFetched( RecordType.NormalMarker, 0 ); } return; } } } } catch ( Exception exception ) { Tracer._TraceException( exception ); } base.EnumerateRecords( fix ); } else { IResultSet rs = _table.CreateResultSet( _table.SortedColumn ); IntArrayList offsets = new IntArrayList(); using( rs ) { foreach( Record rec in rs ) { offsets.Add( rec.Offset ); } } if( _table.Count != offsets.Count ) { throw new InvalidOperationException( "Can't defragment the table, index is corrupted. _table.count = " + _table.Count + ", offsets.Count = " + offsets.Count ); } _table.ClearIndexes(); for( int i = 0; i < offsets.Count; ++i ) { _table.ReadRecordFromOffset( offsets[ i ] ); OnRecordFetched( RecordType.NormalMarker, 0 ); } } } public static void Defragment( Table table ) { lock( table ) { TableDefragmentator defragmentator = new TableDefragmentator( table ); try { defragmentator.EnumerateRecords( false ); defragmentator.Finish(); } catch ( Exception exception ) { Tracer._TraceException( exception ); table.ClearIndexes(); throw new DefragmentationFailedException( exception.Message, exception ); } finally { defragmentator.CleanUp(); } } } protected override int OffsetToWrite { get { return (int)_fileDefragment.Position; } } protected override void OnRecordFetched( RecordType recType, long size ) { base.OnRecordFetched( recType, size ); if ( ( _table.NeedUpgradeTo22 && recType == RecordType.Normal ) || recType == RecordType.NormalMarker ) { _totalCount++; _writerDefragment.Write( (byte)RecordType.NormalMarker ); for ( int i = 0; i < _columns.Length; ++i ) { _columns[i].SaveValue( _writerDefragment ); } } } } public RecordsCounts ComputeWastedSpace( ) { if ( _totalCount != -1 ) { return new RecordsCounts( Count, _totalCount ); } else { RecordsCounts recordsCounts = RecordCounter.GetRecordsCounts( this ); _totalCount = recordsCounts.TotalRecordCount; _tblStructure.SetTotalCount( _totalCount ); return recordsCounts; } } public void AddEntryToCompoundIndexesWithValue( int currentOffset ) { for ( int i = 0; i < _compoundIndexesWithValue.Count; i++ ) { CompoundIndexWithValue compoundIndex = _compoundIndexesWithValue[i] as CompoundIndexWithValue; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; object value1 = compoundIndex._column1.Value; object value2 = compoundIndex._column2.Value; object value3 = compoundIndex._valueColumn.Value; string strValue1 = value1 as string; if ( strValue1 != null ) { value1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = value2 as string; if ( strValue2 != null ) { value2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } SetCompoundKey( _compoundAndValue, value1, value2, value3 ); dbIndex.AddIndexEntry( _compoundAndValue, currentOffset ); } } } public void AddEntryToCompoundIndexes( int currentOffset ) { for ( int i = 0; i < _compoundIndexes.Count; i++ ) { CompoundIndex compoundIndex = _compoundIndexes[i] as CompoundIndex; if ( compoundIndex != null ) { IDBIndex dbIndex = compoundIndex._dbIndex; object value1 = compoundIndex._column1.Value; object value2 = compoundIndex._column2.Value; string strValue1 = value1 as string; if ( strValue1 != null ) { value1 = DBHelper.GetHashCodeInLowerCase( strValue1 ); } string strValue2 = value2 as string; if ( strValue2 != null ) { value2 = DBHelper.GetHashCodeInLowerCase( strValue2 ); } SetCompoundKey( _compound, value1, value2 ); dbIndex.AddIndexEntry( _compound, currentOffset ); } } } public void Dump() { Console.WriteLine( "Table = " + Name ); _file.Position = 0; try { for ( int i = 0; i < _columns.Length; ++i ) { Console.Write( _columns[i].Name + "\t\t" ); } Console.WriteLine( string.Empty ); while ( true ) { RecordType recType = (RecordType)_file.ReadByte(); //read for RecordType for ( int i = 0; i < _columns.Length; ++i ) { Column column = _columns[i]; column.LoadValue( _reader ); if ( recType == RecordType.NormalMarker ) { Console.Write( column.Value.ToString() + "\t\t" ); } } if ( recType == RecordType.NormalMarker ) { Console.WriteLine( string.Empty ); } } } catch ( EndOfStreamException ) { Console.WriteLine( "______________________________________________________________________" ); } } #endregion public void LowLevelCheck() { Console.WriteLine( "Low level checking for '" + Name + "'" ); bool expected = false; try { _file.Position = 0; for ( ;; ) { expected = true; long recordOffset = _file.Position; _file.ReadByte(); expected = false; for ( int i = 0; i < _columns.Length; ++i ) { try { _columns[i].LoadValue( _reader ); } catch ( StringCorruptedException ) { Console.WriteLine( ); Console.WriteLine( "Error: String is corrupted. Record offset = " + recordOffset ); Console.WriteLine( ); return; } catch ( NoEndMarkerException ) { Console.WriteLine( ); Console.WriteLine( "Error: No end marker for string. Record offset = " + recordOffset ); Console.WriteLine( ); return; } catch ( DateTimeCorruptedException ) { Console.WriteLine( ); Console.WriteLine( "Error: DateTime is corrupted. Record offset = " + recordOffset ); Console.WriteLine( ); return; } } } } catch ( EndOfStreamException ) { if ( !expected ) { Console.WriteLine( ); Console.WriteLine( "Error: Unexpected end of file." ); Console.WriteLine( ); } } } } internal class CompoundIndex { public IDBIndex _dbIndex; public Column _column1; public Column _column2; public int _columnIndex1; public int _columnIndex2; public CompoundIndex( IDBIndex dbIndex, Column column1, int columnIndex1, Column column2, int columnIndex2 ) { _dbIndex = dbIndex; _column1 = column1; _column2 = column2; _columnIndex1 = columnIndex1; _columnIndex2 = columnIndex2; } } internal class CompoundIndexWithValue : CompoundIndex { public int _fieldIndex; public Column _valueColumn; public CompoundIndexWithValue( IDBIndex dbIndex, Column column1, int columnIndex1, Column column2, int columnIndex2, Column valueColumn, int fieldIndex ) : base( dbIndex, column1, columnIndex1, column2, columnIndex2 ) { _fieldIndex = fieldIndex; _valueColumn = valueColumn; } } internal class SafeBinaryWriter: BinaryWriter { private Encoder _encoder; private byte[] _charBytes; private const int HALF_BUFFER_SIZE = 512; private const int BUFFER_SIZE = HALF_BUFFER_SIZE*2; internal SafeBinaryWriter( Stream baseStream, Encoding encoding ) : base( baseStream, encoding ) { _encoder = encoding.GetEncoder(); } public void WriteStringSafeWithIntLength( string str ) { if ( _charBytes == null ) { _charBytes = new byte [BUFFER_SIZE]; } int length = str.Length; char[] chars = str.ToCharArray( 0, length ); int byteCount = _encoder.GetByteCount( chars, 0, length, true ); Write( byteCount ); int curPos = 0; while ( length > 0 ) { int toRead = length < HALF_BUFFER_SIZE ? length : HALF_BUFFER_SIZE; length -= toRead; byteCount = _encoder.GetByteCount( chars, curPos, toRead, true ); byte[] buffer = byteCount > BUFFER_SIZE ? new byte[byteCount] : _charBytes; int n = _encoder.GetBytes( chars, curPos, toRead, buffer, 0, true ); Write( buffer, 0, n ); curPos += toRead; } } } /// /// A bugfix for BinaryReader which fixes the "Conversion buffer overflow" issue /// http://blogs.jetbrains.com/yole/archives/000035.html /// internal class SafeBinaryReader: BinaryReader { private byte[] _charBytes; private char[] _charBuffer; private Decoder _decoder; internal SafeBinaryReader( Stream baseStream, Encoding encoding ) : base( baseStream, encoding ) { _decoder = encoding.GetDecoder(); } public string ReadStringSafeWithoutLength( int stringLength ) { if ( stringLength < 0 ) { throw new StringCorruptedException( "string length was negative: " + stringLength ); } if ( _charBytes == null ) { _charBytes = new byte [128]; } if ( _charBuffer == null ) { _charBuffer = new char [256]; } int currPos = 0; StringBuilder sb = null; try { do { int readLength = ((stringLength - currPos) > 128) ? 128 : stringLength - currPos; int n = Read( _charBytes, 0, readLength ); if ( n == 0 ) { throw new EndOfStreamException(); } int charsRead = _decoder.GetChars( _charBytes, 0, n, _charBuffer, 0 ); if ( currPos == 0 && n == stringLength ) return new string( _charBuffer, 0, charsRead ); if ( sb == null ) { sb = StringBuilderPool.Alloc(); } sb.Append( _charBuffer, 0, charsRead ); currPos += n; } while( currPos < stringLength ); return sb.ToString(); } finally { if( sb != null ) { StringBuilderPool.Dispose( sb ); } } } public string ReadStringSafe() { int stringLength = Read7BitEncodedInt(); if ( stringLength == 0 ) { return String.Empty; } return ReadStringSafeWithoutLength( stringLength ); } } public class Tests { public static void ReadRecordFromOffset( ITable table, int offset ) { ( table as Table ).ReadRecordFromOffset( offset ); } public static void DeleteRecord( ITable table, int offset, object[] fields ) { ( table as Table ).DeleteRecord( offset, fields ); } } }