diff --git a/SharpCoreDB.Tests/DatabaseFileTests.cs b/SharpCoreDB.Tests/DatabaseFileTests.cs index 3cbb8735..a69a6fd7 100644 --- a/SharpCoreDB.Tests/DatabaseFileTests.cs +++ b/SharpCoreDB.Tests/DatabaseFileTests.cs @@ -49,11 +49,18 @@ public void ReadWritePage_ShouldWorkCorrectly() { // Arrange using var dbFile = new DatabaseFile(this.testFilePath, this.crypto, this.key); + + // Create test data with valid page header var testData = new byte[4096]; - for (int i = 0; i < testData.Length; i++) + var header = PageHeader.Create((byte)PageType.Data, 12345); + var userData = new byte[4096 - PageHeader.Size]; + for (int i = 0; i < userData.Length; i++) { - testData[i] = (byte)(i % 256); + userData[i] = (byte)(i % 256); } + + // Create complete page using PageSerializer + PageSerializer.CreatePage(ref header, userData, testData); // Act dbFile.WritePage(0, testData); @@ -71,11 +78,18 @@ public void ReadPageZeroAlloc_ShouldReadIntoBuffer() { // Arrange using var dbFile = new DatabaseFile(this.testFilePath, this.crypto, this.key); + + // Create test data with valid page header var testData = new byte[4096]; - for (int i = 0; i < testData.Length; i++) + var header = PageHeader.Create((byte)PageType.Data, 12345); + var userData = new byte[4096 - PageHeader.Size]; + for (int i = 0; i < userData.Length; i++) { - testData[i] = (byte)(i % 256); + userData[i] = (byte)(i % 256); } + + // Create complete page using PageSerializer + PageSerializer.CreatePage(ref header, userData, testData); var buffer = new byte[4096]; // Act diff --git a/SharpCoreDB.Tests/HashIndexTests.cs b/SharpCoreDB.Tests/HashIndexTests.cs index f0c910ea..5da853fc 100644 --- a/SharpCoreDB.Tests/HashIndexTests.cs +++ b/SharpCoreDB.Tests/HashIndexTests.cs @@ -60,7 +60,7 @@ public void HashIndex_Remove_RemovesRow() index.Add(row2, 1); var beforeRemove = index.LookupPositions(1); - index.Remove(row1); + index.Remove(row1, 0); var afterRemove = index.LookupPositions(1); // Assert diff --git a/SharpCoreDB.Tests/IndexTests.cs b/SharpCoreDB.Tests/IndexTests.cs index ee213ced..4b72b1ee 100644 --- a/SharpCoreDB.Tests/IndexTests.cs +++ b/SharpCoreDB.Tests/IndexTests.cs @@ -197,7 +197,7 @@ public void HashIndex_UpdateOperations_MaintainConsistency() // Act - Update status var updatedRow = new Dictionary { { "id", 1 }, { "status", "inactive" }, { "name", "Item1" } }; - index.Remove(rows[0]); + index.Remove(rows[0], 0); index.Add(updatedRow, 0); // Assert - Index should reflect changes diff --git a/SharpCoreDB/DataStructures/HashIndex.cs b/SharpCoreDB/DataStructures/HashIndex.cs index 47c51ccc..6a449818 100644 --- a/SharpCoreDB/DataStructures/HashIndex.cs +++ b/SharpCoreDB/DataStructures/HashIndex.cs @@ -17,7 +17,7 @@ namespace SharpCoreDB.DataStructures; /// public class HashIndex { - private readonly Dictionary> _index = new(); + private readonly Dictionary> _index; private readonly string _columnName; private readonly SimdHashEqualityComparer _comparer = new(); @@ -29,6 +29,10 @@ public class HashIndex public HashIndex(string tableName, string columnName) { _columnName = columnName; + // Use default comparer: SimdHashEqualityComparer was removed due to issues with + // reference equality vs value equality for boxed value types on Linux/.NET 10. + // May revisit with proper generic implementation in future. + _index = new Dictionary>(); } /// @@ -39,7 +43,7 @@ public HashIndex(string tableName, string columnName) [MethodImpl(MethodImplOptions.AggressiveOptimization)] public void Add(Dictionary row, long position) { - if (!row.TryGetValue(_columnName, out var key)) + if (!row.TryGetValue(_columnName, out var key) || key == null) return; if (!_index.TryGetValue(key, out var list)) @@ -56,12 +60,32 @@ public void Add(Dictionary row, long position) /// The row data. public void Remove(Dictionary row) { - if (row.TryGetValue(_columnName, out var key)) + if (row.TryGetValue(_columnName, out var key) && key != null) { _index.Remove(key); } } + /// + /// Removes a specific position for a row from the index. + /// + /// The row data. + /// The position to remove. + public void Remove(Dictionary row, long position) + { + if (!row.TryGetValue(_columnName, out var key) || key == null) + return; + + if (_index.TryGetValue(key, out var list)) + { + list.Remove(position); + if (list.Count == 0) + { + _index.Remove(key); + } + } + } + /// /// Looks up positions for a given key using SIMD-accelerated comparison. /// @@ -69,7 +93,7 @@ public void Remove(Dictionary row) /// List of positions matching the key. [MethodImpl(MethodImplOptions.AggressiveOptimization)] public List LookupPositions(object key) - => _index.TryGetValue(key, out var list) ? list : new(); + => key != null && _index.TryGetValue(key, out var list) ? new List(list) : new(); /// /// Gets the number of unique keys in the index. diff --git a/SharpCoreDB/DataStructures/Table.cs b/SharpCoreDB/DataStructures/Table.cs index 206ba658..02b7eeca 100644 --- a/SharpCoreDB/DataStructures/Table.cs +++ b/SharpCoreDB/DataStructures/Table.cs @@ -228,10 +228,35 @@ private List> SelectInternal(string? where, string? o { var idx = this.Columns.IndexOf(orderBy); if (idx >= 0) - results = asc ? results.OrderBy(r => r[this.Columns[idx]]).ToList() : results.OrderByDescending(r => r[this.Columns[idx]]).ToList(); + { + var columnName = this.Columns[idx]; + // Use Comparer.Default which handles type mismatches gracefully + results = asc + ? results.OrderBy(r => r[columnName], Comparer.Create((a, b) => CompareObjects(a, b))).ToList() + : results.OrderByDescending(r => r[columnName], Comparer.Create((a, b) => CompareObjects(a, b))).ToList(); + } } return results; } + + private static int CompareObjects(object? a, object? b) + { + // Handle nulls + if (a == null && b == null) return 0; + if (a == null) return -1; + if (b == null) return 1; + + // If same type, use default comparison + if (a.GetType() == b.GetType()) + { + if (a is IComparable ca) + return ca.CompareTo(b); + return 0; + } + + // Different types - compare as strings + return string.Compare(a.ToString() ?? "", b.ToString() ?? "", StringComparison.Ordinal); + } /// /// SIMD-accelerated row scanning for full table scans. diff --git a/SharpCoreDB/Services/EnhancedSqlParser.cs b/SharpCoreDB/Services/EnhancedSqlParser.cs index 75aecfd2..f9d0f4eb 100644 --- a/SharpCoreDB/Services/EnhancedSqlParser.cs +++ b/SharpCoreDB/Services/EnhancedSqlParser.cs @@ -293,7 +293,16 @@ private FromNode ParseFrom() } else { - node.TableName = ConsumeIdentifier() ?? ""; + var tableName = ConsumeIdentifier(); + if (tableName == null) + { + RecordError("Expected table name after FROM"); + node.TableName = ""; + } + else + { + node.TableName = tableName; + } } // Parse alias diff --git a/SharpCoreDB/SharpCoreDB/Core/File/DatabaseFile.cs b/SharpCoreDB/SharpCoreDB/Core/File/DatabaseFile.cs index 4ccac5f3..ba927a22 100644 --- a/SharpCoreDB/SharpCoreDB/Core/File/DatabaseFile.cs +++ b/SharpCoreDB/SharpCoreDB/Core/File/DatabaseFile.cs @@ -130,7 +130,7 @@ public void WritePageFromSpan(int pageNum, ReadOnlySpan data) // Copy to encryption buffer and encrypt in-place data.CopyTo(_encryptedBuffer.AsSpan(0, PageSize)); - this.crypto.EncryptPage(_encryptedBuffer.AsSpan(0, PageSize)); + this.crypto.EncryptPage(_encryptedBuffer.AsSpan(0, StoredPageSize)); // Write to disk using var fs = new FileStream(this.filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 4096, FileOptions.WriteThrough);