diff --git a/README.md b/README.md
index ad3f25da..cf372900 100644
--- a/README.md
+++ b/README.md
@@ -662,19 +662,39 @@ var config = new DatabaseConfig
### 📊 Reproduce These Benchmarks
+Run comprehensive benchmarks comparing SharpCoreDB (encrypted/unencrypted) against SQLite and LiteDB:
+
```bash
-# All benchmarks
cd SharpCoreDB.Benchmarks
-dotnet run -c Release
-# Specific operations
+# Quick comparison (recommended for testing)
+dotnet run -c Release -- --quick
+
+# Full comprehensive suite (all operations, all sizes)
+dotnet run -c Release -- --full
+
+# Specific benchmark categories
+dotnet run -c Release -- --inserts # INSERT operations
+dotnet run -c Release -- --selects # SELECT operations
+dotnet run -c Release -- --updates # UPDATE/DELETE operations
+dotnet run -c Release -- --aggregates # COUNT, SUM, AVG, MIN, MAX, GROUP BY
+
+# Using BenchmarkDotNet filters
dotnet run -c Release -- --filter "*Insert*"
dotnet run -c Release -- --filter "*Select*"
-dotnet run -c Release -- --filter "*Update*"
-dotnet run -c Release -- --filter "*Delete*"
+dotnet run -c Release -- --filter "*Aggregate*"
```
-**Detailed Results**: See `COMPREHENSIVE_BENCHMARK_SECTION.md` for full analysis
+**What gets benchmarked:**
+- ✅ **INSERT**: Individual inserts, batch inserts, prepared statements, true batch (single transaction)
+- ✅ **SELECT**: Point queries, range queries, full table scans
+- ✅ **UPDATE/DELETE**: Bulk updates, bulk deletes
+- ✅ **AGGREGATES**: COUNT, SUM, AVG, MIN, MAX, GROUP BY operations
+- ✅ **All operations tested with and without encryption**
+
+**Detailed Documentation**:
+- See [`SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md`](SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md) for complete usage guide
+- Results automatically saved to `BenchmarkDotNet.Artifacts/results/` in HTML, CSV, JSON, and Markdown formats
---
diff --git a/SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md b/SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md
index a2c7bf24..5acf659f 100644
--- a/SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md
+++ b/SharpCoreDB.Benchmarks/COMPARATIVE_BENCHMARKS_README.md
@@ -12,12 +12,14 @@ This benchmark suite provides head-to-head performance comparisons across three
## Features
-? **Automatic README Updates** - Results automatically inserted into root README.md
-? **Multiple Test Scenarios** - INSERT, SELECT, UPDATE, DELETE operations
-? **Various Data Sizes** - 1, 10, 100, 1K, 10K, 100K records
-? **Memory Diagnostics** - Track allocations and GC pressure
-? **Performance Charts** - Auto-generated charts using RPlot
-? **Statistical Analysis** - Mean, median, standard deviation
+✅ **Automatic README Updates** - Results automatically inserted into root README.md
+✅ **Multiple Test Scenarios** - INSERT, SELECT, UPDATE, DELETE, AGGREGATES operations
+✅ **Various Data Sizes** - 1, 10, 100, 1K, 10K records
+✅ **Memory Diagnostics** - Track allocations and GC pressure
+✅ **Performance Charts** - Auto-generated charts using RPlot
+✅ **Statistical Analysis** - Mean, median, standard deviation
+✅ **Encryption Impact** - Compare encrypted vs unencrypted performance
+✅ **Fair Comparisons** - Same data, same operations across all engines
## Running Benchmarks
@@ -45,6 +47,9 @@ dotnet run -c Release -- --filter Select
# Update/Delete benchmarks only
dotnet run -c Release -- --filter Update
+
+# Aggregate benchmarks only
+dotnet run -c Release -- --filter Aggregate
```
## Benchmark Categories
@@ -53,12 +58,18 @@ dotnet run -c Release -- --filter Update
Tests single and bulk insert performance across different record counts.
-**Test Sizes**: 1, 10, 100, 1,000, 10,000 records
+**Test Sizes**: 1, 10, 100, 1,000 records
**Scenarios**:
-- SharpCoreDB bulk insert
+- SharpCoreDB (encrypted) - Individual inserts
+- SharpCoreDB (encrypted) - Batch inserts (prepared statements)
+- SharpCoreDB (encrypted) - True batch inserts (single transaction)
+- SharpCoreDB (no encryption) - Individual inserts
+- SharpCoreDB (no encryption) - Batch inserts (prepared statements)
+- SharpCoreDB (no encryption) - True batch inserts (single transaction)
- SQLite memory bulk insert (baseline)
- SQLite file bulk insert
+- SQLite file + WAL + FullSync bulk insert
- LiteDB bulk insert
**Example Results**:
@@ -75,7 +86,7 @@ Tests single and bulk insert performance across different record counts.
Tests query performance for point queries, range filters, and full scans.
-**Database Size**: 10,000 pre-populated records
+**Database Size**: 1,000 pre-populated records
**Scenarios**:
- Point query by ID
@@ -95,7 +106,7 @@ Tests query performance for point queries, range filters, and full scans.
Tests modification and deletion performance.
-**Test Sizes**: 1, 10, 100, 1,000 records
+**Test Sizes**: 1, 10, 100 records
**Scenarios**:
- Bulk updates
@@ -110,6 +121,41 @@ Tests modification and deletion performance.
| SharpCoreDB_Update | 100 | 4.1 ms |
```
+### 4. Aggregate Benchmarks (`ComparativeAggregateBenchmarks.cs`)
+
+Tests aggregate function performance across all database engines.
+
+**Database Size**: 10,000 pre-populated records
+
+**Scenarios**:
+- COUNT(*) - Full table count
+- COUNT(*) WHERE - Filtered count
+- SUM(age) - Sum aggregation
+- AVG(age) - Average calculation
+- MIN(age) - Minimum value
+- MAX(age) - Maximum value
+- GROUP BY age - Grouping with count
+- Complex aggregates - Multiple operations with filters
+
+**Databases Tested**:
+- SharpCoreDB (with AES-256-GCM encryption)
+- SharpCoreDB (without encryption)
+- SQLite (file-based)
+- LiteDB (with manual LINQ aggregations)
+
+**Important Note on LiteDB Aggregates**:
+LiteDB doesn't have native SQL aggregate functions like SUM, AVG, MIN, MAX. The benchmarks use LINQ methods (`FindAll().Sum()`, etc.) which load all records into memory first, making them less efficient than true database-level aggregates. This is a fundamental limitation of LiteDB's architecture, not a benchmark design flaw.
+
+**Example Results**:
+```
+| Method | Mean | Allocated |
+|---------------------------------|-------------|------------|
+| SQLite_CountAll | 125 ?s | 512 B |
+| SharpCoreDB_NoEncrypt_CountAll | 180 ?s | 1 KB |
+| SharpCoreDB_Encrypted_CountAll | 210 ?s | 1.2 KB |
+| LiteDB_CountAll | 150 ?s | 768 B |
+```
+
## Output Structure
After running benchmarks, the following artifacts are generated:
diff --git a/SharpCoreDB.Benchmarks/Comparative/ComparativeAggregateBenchmarks.cs b/SharpCoreDB.Benchmarks/Comparative/ComparativeAggregateBenchmarks.cs
new file mode 100644
index 00000000..196919f7
--- /dev/null
+++ b/SharpCoreDB.Benchmarks/Comparative/ComparativeAggregateBenchmarks.cs
@@ -0,0 +1,510 @@
+//
+// Copyright (c) 2025-2026 MPCoreDeveloper and GitHub Copilot. All rights reserved.
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Order;
+using SharpCoreDB.Benchmarks.Infrastructure;
+using Microsoft.Data.Sqlite;
+using LiteDB;
+using System.Diagnostics;
+
+namespace SharpCoreDB.Benchmarks.Comparative;
+
+///
+/// Comparative benchmarks for aggregate operations (COUNT, SUM, AVG, MIN, MAX, GROUP BY).
+/// Tests SharpCoreDB (encrypted + non-encrypted) vs SQLite vs LiteDB.
+///
+[Config(typeof(BenchmarkConfig))]
+[MemoryDiagnoser]
+[Orderer(SummaryOrderPolicy.FastestToSlowest)]
+[RankColumn]
+public class ComparativeAggregateBenchmarks : IDisposable
+{
+ private TestDataGenerator dataGenerator = null!;
+ private string tempDir = null!;
+ private const int TotalRecords = 10000;
+
+ // SharpCoreDB - Encrypted
+ private BenchmarkDatabaseHelper? sharpCoreDbEncrypted;
+
+ // SharpCoreDB - No Encryption
+ private BenchmarkDatabaseHelper? sharpCoreDbNoEncrypt;
+
+ // SQLite
+ private SqliteConnection? sqliteConn;
+
+ // LiteDB
+ private LiteDatabase? liteDb;
+ private ILiteCollection? liteCollection;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Console.WriteLine($"\n{new string('=', 60)}");
+ Console.WriteLine("AGGREGATE Benchmarks - Starting Setup");
+ Console.WriteLine($"{new string('=', 60)}");
+
+ dataGenerator = new TestDataGenerator();
+ tempDir = Path.Combine(Path.GetTempPath(), $"dbBenchmark_{Guid.NewGuid()}");
+ Directory.CreateDirectory(tempDir);
+
+ var totalSw = Stopwatch.StartNew();
+
+ SetupAndPopulateSharpCoreDB();
+ SetupAndPopulateSQLite();
+ SetupAndPopulateLiteDB();
+
+ totalSw.Stop();
+
+ Console.WriteLine($"\n✅ Total setup time: {totalSw.ElapsedMilliseconds}ms");
+ Console.WriteLine($"{new string('=', 60)}\n");
+ }
+
+ private void SetupAndPopulateSharpCoreDB()
+ {
+ Console.WriteLine("\n🔧 Setting up SharpCoreDB...");
+
+ try
+ {
+ // ========== ENCRYPTED VARIANT ==========
+ var swEncrypted = Stopwatch.StartNew();
+
+ var dbPathEncrypted = Path.Combine(tempDir, "sharpcore_encrypted");
+ sharpCoreDbEncrypted = new BenchmarkDatabaseHelper(dbPathEncrypted, enableEncryption: true);
+ sharpCoreDbEncrypted.CreateUsersTable();
+
+ // Populate with batch insert for fast setup
+ var usersEncrypted = dataGenerator.GenerateUsers(TotalRecords);
+ var userListEncrypted = usersEncrypted.Select(u =>
+ (u.Id, u.Name, u.Email, u.Age, u.CreatedAt, u.IsActive)
+ ).ToList();
+
+ sharpCoreDbEncrypted.InsertUsersTrueBatch(userListEncrypted);
+
+ swEncrypted.Stop();
+ Console.WriteLine($" ✓ SharpCoreDB (Encrypted): {TotalRecords} records in {swEncrypted.ElapsedMilliseconds}ms");
+
+ // ========== NO ENCRYPTION VARIANT ==========
+ var swNoEncrypt = Stopwatch.StartNew();
+
+ var dbPathNoEncrypt = Path.Combine(tempDir, "sharpcore_noencrypt");
+ sharpCoreDbNoEncrypt = new BenchmarkDatabaseHelper(dbPathNoEncrypt, enableEncryption: false);
+ sharpCoreDbNoEncrypt.CreateUsersTable();
+
+ // Populate with batch insert
+ var usersNoEncrypt = dataGenerator.GenerateUsers(TotalRecords);
+ var userListNoEncrypt = usersNoEncrypt.Select(u =>
+ (u.Id, u.Name, u.Email, u.Age, u.CreatedAt, u.IsActive)
+ ).ToList();
+
+ sharpCoreDbNoEncrypt.InsertUsersTrueBatch(userListNoEncrypt);
+
+ swNoEncrypt.Stop();
+ Console.WriteLine($" ✓ SharpCoreDB (No Encryption): {TotalRecords} records in {swNoEncrypt.ElapsedMilliseconds}ms");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"❌ SharpCoreDB setup failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ private void SetupAndPopulateSQLite()
+ {
+ Console.WriteLine("\n🔧 Setting up SQLite...");
+
+ var sw = Stopwatch.StartNew();
+
+ var sqlitePath = Path.Combine(tempDir, "sqlite.db");
+ sqliteConn = new SqliteConnection($"Data Source={sqlitePath}");
+ sqliteConn.Open();
+
+ // Create table
+ using var createCmd = sqliteConn.CreateCommand();
+ createCmd.CommandText = @"
+ CREATE TABLE users (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ email TEXT NOT NULL,
+ age INTEGER NOT NULL,
+ created_at TEXT NOT NULL,
+ is_active INTEGER NOT NULL
+ )";
+ createCmd.ExecuteNonQuery();
+
+ // Create indexes for better aggregate performance
+ createCmd.CommandText = "CREATE INDEX idx_age ON users(age)";
+ createCmd.ExecuteNonQuery();
+ createCmd.CommandText = "CREATE INDEX idx_is_active ON users(is_active)";
+ createCmd.ExecuteNonQuery();
+
+ // Populate
+ var users = dataGenerator.GenerateUsers(TotalRecords);
+ using var transaction = sqliteConn.BeginTransaction();
+ using var insertCmd = sqliteConn.CreateCommand();
+ insertCmd.CommandText = @"
+ INSERT INTO users (id, name, email, age, created_at, is_active)
+ VALUES (@id, @name, @email, @age, @created_at, @is_active)";
+
+ insertCmd.Parameters.Add("@id", SqliteType.Integer);
+ insertCmd.Parameters.Add("@name", SqliteType.Text);
+ insertCmd.Parameters.Add("@email", SqliteType.Text);
+ insertCmd.Parameters.Add("@age", SqliteType.Integer);
+ insertCmd.Parameters.Add("@created_at", SqliteType.Text);
+ insertCmd.Parameters.Add("@is_active", SqliteType.Integer);
+
+ foreach (var user in users)
+ {
+ insertCmd.Parameters["@id"].Value = user.Id;
+ insertCmd.Parameters["@name"].Value = user.Name;
+ insertCmd.Parameters["@email"].Value = user.Email;
+ insertCmd.Parameters["@age"].Value = user.Age;
+ insertCmd.Parameters["@created_at"].Value = user.CreatedAt.ToString("o");
+ insertCmd.Parameters["@is_active"].Value = user.IsActive ? 1 : 0;
+ insertCmd.ExecuteNonQuery();
+ }
+
+ transaction.Commit();
+
+ sw.Stop();
+ Console.WriteLine($" ✓ SQLite: {TotalRecords} records in {sw.ElapsedMilliseconds}ms");
+ }
+
+ private void SetupAndPopulateLiteDB()
+ {
+ Console.WriteLine("\n🔧 Setting up LiteDB...");
+
+ var sw = Stopwatch.StartNew();
+
+ var liteDbPath = Path.Combine(tempDir, "litedb.db");
+ liteDb = new LiteDatabase(liteDbPath);
+ liteCollection = liteDb.GetCollection("users");
+
+ // Create indexes
+ liteCollection.EnsureIndex(x => x.Age);
+ liteCollection.EnsureIndex(x => x.IsActive);
+
+ // Populate
+ var users = dataGenerator.GenerateUsers(TotalRecords);
+ liteCollection.InsertBulk(users);
+
+ sw.Stop();
+ Console.WriteLine($" ✓ LiteDB: {TotalRecords} records in {sw.ElapsedMilliseconds}ms");
+ }
+
+ // ==================== COUNT OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): COUNT(*)")]
+ public int SharpCoreDB_Encrypted_CountAll()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT COUNT(*) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): COUNT(*)")]
+ public int SharpCoreDB_NoEncrypt_CountAll()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT COUNT(*) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Baseline = true, Description = "SQLite: COUNT(*)")]
+ public long SQLite_CountAll()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT COUNT(*) FROM users";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToInt64(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: COUNT()")]
+ public int LiteDB_CountAll()
+ {
+ return liteCollection?.Count() ?? 0;
+ }
+
+ // ==================== COUNT WITH FILTER ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): COUNT WHERE")]
+ public int SharpCoreDB_Encrypted_CountWhere()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT COUNT(*) FROM users WHERE age > 30");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): COUNT WHERE")]
+ public int SharpCoreDB_NoEncrypt_CountWhere()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT COUNT(*) FROM users WHERE age > 30");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: COUNT WHERE")]
+ public long SQLite_CountWhere()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT COUNT(*) FROM users WHERE age > 30";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToInt64(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: COUNT WHERE")]
+ public int LiteDB_CountWhere()
+ {
+ return liteCollection?.Count(x => x.Age > 30) ?? 0;
+ }
+
+ // ==================== SUM OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): SUM(age)")]
+ public int SharpCoreDB_Encrypted_Sum()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT SUM(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): SUM(age)")]
+ public int SharpCoreDB_NoEncrypt_Sum()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT SUM(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: SUM(age)")]
+ public long SQLite_Sum()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT SUM(age) FROM users";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToInt64(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: SUM(age) - Manual LINQ")]
+ public int LiteDB_Sum()
+ {
+ // NOTE: LiteDB doesn't have native SQL SUM, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ return liteCollection?.FindAll().Sum(x => x.Age) ?? 0;
+ }
+
+ // ==================== AVG OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): AVG(age)")]
+ public int SharpCoreDB_Encrypted_Avg()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT AVG(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): AVG(age)")]
+ public int SharpCoreDB_NoEncrypt_Avg()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT AVG(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: AVG(age)")]
+ public double SQLite_Avg()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT AVG(age) FROM users";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToDouble(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: AVG(age) - Manual LINQ")]
+ public double LiteDB_Avg()
+ {
+ // NOTE: LiteDB doesn't have native SQL AVG, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ return liteCollection?.FindAll().Average(x => x.Age) ?? 0;
+ }
+
+ // ==================== MIN OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): MIN(age)")]
+ public int SharpCoreDB_Encrypted_Min()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT MIN(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): MIN(age)")]
+ public int SharpCoreDB_NoEncrypt_Min()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT MIN(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: MIN(age)")]
+ public long SQLite_Min()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT MIN(age) FROM users";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToInt64(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: MIN(age) - Manual LINQ")]
+ public int LiteDB_Min()
+ {
+ // NOTE: LiteDB doesn't have native SQL MIN, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ return liteCollection?.FindAll().Min(x => x.Age) ?? 0;
+ }
+
+ // ==================== MAX OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): MAX(age)")]
+ public int SharpCoreDB_Encrypted_Max()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT MAX(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): MAX(age)")]
+ public int SharpCoreDB_NoEncrypt_Max()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT MAX(age) FROM users");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: MAX(age)")]
+ public long SQLite_Max()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT MAX(age) FROM users";
+ var result = cmd.ExecuteScalar();
+ return result != null ? Convert.ToInt64(result) : 0;
+ }
+
+ [Benchmark(Description = "LiteDB: MAX(age) - Manual LINQ")]
+ public int LiteDB_Max()
+ {
+ // NOTE: LiteDB doesn't have native SQL MAX, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ return liteCollection?.FindAll().Max(x => x.Age) ?? 0;
+ }
+
+ // ==================== GROUP BY OPERATIONS ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): GROUP BY age")]
+ public int SharpCoreDB_Encrypted_GroupBy()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery("SELECT age, COUNT(*) FROM users GROUP BY age");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): GROUP BY age")]
+ public int SharpCoreDB_NoEncrypt_GroupBy()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery("SELECT age, COUNT(*) FROM users GROUP BY age");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: GROUP BY age")]
+ public int SQLite_GroupBy()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = "SELECT age, COUNT(*) FROM users GROUP BY age";
+ using var reader = cmd.ExecuteReader();
+ int count = 0;
+ while (reader.Read()) { count++; }
+ return count;
+ }
+
+ [Benchmark(Description = "LiteDB: GROUP BY age - Manual LINQ")]
+ public int LiteDB_GroupBy()
+ {
+ // NOTE: LiteDB doesn't have native SQL GROUP BY, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ var groups = liteCollection?.FindAll().GroupBy(x => x.Age).ToList();
+ return groups?.Count ?? 0;
+ }
+
+ // ==================== COMPLEX AGGREGATES ====================
+
+ [Benchmark(Description = "SharpCoreDB (Encrypted): Complex Aggregate")]
+ public int SharpCoreDB_Encrypted_ComplexAggregate()
+ {
+ var results = sharpCoreDbEncrypted?.ExecuteQuery(@"
+ SELECT age, COUNT(*) as cnt, AVG(age) as avg_age
+ FROM users
+ WHERE is_active = 1
+ GROUP BY age");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SharpCoreDB (No Encryption): Complex Aggregate")]
+ public int SharpCoreDB_NoEncrypt_ComplexAggregate()
+ {
+ var results = sharpCoreDbNoEncrypt?.ExecuteQuery(@"
+ SELECT age, COUNT(*) as cnt, AVG(age) as avg_age
+ FROM users
+ WHERE is_active = 1
+ GROUP BY age");
+ return results?.Count ?? 0;
+ }
+
+ [Benchmark(Description = "SQLite: Complex Aggregate")]
+ public int SQLite_ComplexAggregate()
+ {
+ using var cmd = sqliteConn?.CreateCommand();
+ cmd!.CommandText = @"
+ SELECT age, COUNT(*) as cnt, AVG(age) as avg_age
+ FROM users
+ WHERE is_active = 1
+ GROUP BY age";
+ using var reader = cmd.ExecuteReader();
+ int count = 0;
+ while (reader.Read()) { count++; }
+ return count;
+ }
+
+ [Benchmark(Description = "LiteDB: Complex Aggregate - Manual LINQ")]
+ public int LiteDB_ComplexAggregate()
+ {
+ // NOTE: LiteDB doesn't have native SQL GROUP BY, so we use LINQ
+ // This is less efficient as it loads all records into memory first
+ var result = liteCollection?
+ .Find(x => x.IsActive)
+ .GroupBy(x => x.Age)
+ .Select(g => new
+ {
+ Age = g.Key,
+ Count = g.Count(),
+ AvgAge = g.Average(x => x.Age)
+ })
+ .ToList();
+ return result?.Count ?? 0;
+ }
+
+ [GlobalCleanup]
+ public void Cleanup()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ sharpCoreDbEncrypted?.Dispose();
+ sharpCoreDbNoEncrypt?.Dispose();
+ sqliteConn?.Dispose();
+ liteDb?.Dispose();
+
+ try
+ {
+ if (Directory.Exists(tempDir))
+ {
+ Directory.Delete(tempDir, recursive: true);
+ }
+ }
+ catch
+ {
+ // Ignore cleanup errors
+ }
+
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/SharpCoreDB.Benchmarks/ComprehensiveBenchmarkRunner.cs b/SharpCoreDB.Benchmarks/ComprehensiveBenchmarkRunner.cs
index 5c36a7f0..c69c5cb0 100644
--- a/SharpCoreDB.Benchmarks/ComprehensiveBenchmarkRunner.cs
+++ b/SharpCoreDB.Benchmarks/ComprehensiveBenchmarkRunner.cs
@@ -24,17 +24,18 @@ public static void Run(string[] args)
Console.WriteLine("???????????????????????????????????????????????????????????????");
Console.WriteLine();
Console.WriteLine("Comparing:");
- Console.WriteLine(" SharpCoreDB (WITH encryption)");
- Console.WriteLine(" SharpCoreDB (WITHOUT encryption)");
- Console.WriteLine(" SQLite (Memory mode)");
- Console.WriteLine(" SQLite (File mode)");
- Console.WriteLine(" LiteDB");
+ Console.WriteLine(" � SharpCoreDB (WITH encryption)");
+ Console.WriteLine(" � SharpCoreDB (WITHOUT encryption)");
+ Console.WriteLine(" � SQLite (Memory mode)");
+ Console.WriteLine(" � SQLite (File mode)");
+ Console.WriteLine(" � LiteDB");
Console.WriteLine();
Console.WriteLine("Operations tested:");
- Console.WriteLine(" INSERT (individual & batch)");
- Console.WriteLine(" SELECT (point queries, range queries, full scans)");
- Console.WriteLine(" UPDATE (bulk updates)");
- Console.WriteLine(" DELETE (bulk deletes)");
+ Console.WriteLine(" � INSERT (individual & batch)");
+ Console.WriteLine(" � SELECT (point queries, range queries, full scans)");
+ Console.WriteLine(" � UPDATE (bulk updates)");
+ Console.WriteLine(" � DELETE (bulk deletes)");
+ Console.WriteLine(" � AGGREGATES (COUNT, SUM, AVG, MIN, MAX, GROUP BY)");
Console.WriteLine("???????????????????????????????????????????????????????????????");
Console.WriteLine();
@@ -63,6 +64,11 @@ public static void Run(string[] args)
Console.WriteLine("?? UPDATE/DELETE ONLY: Running update and delete benchmarks...\n");
RunUpdateDeleteBenchmarks();
}
+ else if (args.Length > 0 && args[0] == "--aggregates")
+ {
+ Console.WriteLine("?? AGGREGATES ONLY: Running aggregate benchmarks...\n");
+ RunAggregateBenchmarks();
+ }
else
{
ShowMenu();
@@ -79,7 +85,8 @@ private static void ShowMenu()
Console.WriteLine(" 3. Insert Benchmarks Only");
Console.WriteLine(" 4. Select Benchmarks Only");
Console.WriteLine(" 5. Update/Delete Benchmarks Only");
- Console.WriteLine(" 6. All Benchmarks (Sequential)");
+ Console.WriteLine(" 6. Aggregate Benchmarks Only");
+ Console.WriteLine(" 7. All Benchmarks (Sequential)");
Console.WriteLine(" Q. Quit");
Console.WriteLine();
Console.Write("Choice: ");
@@ -104,6 +111,9 @@ private static void ShowMenu()
RunUpdateDeleteBenchmarks();
return;
case "6":
+ RunAggregateBenchmarks();
+ return;
+ case "7":
RunAllBenchmarks();
return;
case "Q":
@@ -136,6 +146,10 @@ private static void RunQuickMode()
var updateSummary = BenchmarkRunner.Run();
summaries.Add(updateSummary);
+ Console.WriteLine("\n?? Running AGGREGATE benchmarks...");
+ var aggregateSummary = BenchmarkRunner.Run();
+ summaries.Add(aggregateSummary);
+
Console.WriteLine("\n???????????????????????????????????????????????????????????????");
Console.WriteLine(" RESULTS SUMMARY");
Console.WriteLine("???????????????????????????????????????????????????????????????\n");
@@ -159,10 +173,14 @@ private static void RunFullMode()
var selectSummary = BenchmarkRunner.Run();
summaries.Add(selectSummary);
- Console.WriteLine("\n?? Phase 3/3: UPDATE/DELETE benchmarks...");
+ Console.WriteLine("\n?? Phase 3/4: UPDATE/DELETE benchmarks...");
var updateSummary = BenchmarkRunner.Run();
summaries.Add(updateSummary);
+ Console.WriteLine("\n?? Phase 4/4: AGGREGATE benchmarks (COUNT, SUM, AVG, MIN, MAX, GROUP BY)...");
+ var aggregateSummary = BenchmarkRunner.Run();
+ summaries.Add(aggregateSummary);
+
Console.WriteLine("\n???????????????????????????????????????????????????????????????");
Console.WriteLine(" COMPREHENSIVE RESULTS");
Console.WriteLine("???????????????????????????????????????????????????????????????\n");
@@ -192,6 +210,13 @@ private static void RunUpdateDeleteBenchmarks()
GenerateComprehensiveReport(new List { summary });
}
+ private static void RunAggregateBenchmarks()
+ {
+ Console.WriteLine("?? Running AGGREGATE benchmarks...\n");
+ var summary = BenchmarkRunner.Run();
+ GenerateComprehensiveReport(new List { summary });
+ }
+
private static void RunAllBenchmarks()
{
Console.WriteLine("?? Running ALL benchmarks sequentially...\n");
@@ -310,10 +335,10 @@ private static void GenerateComprehensiveReport(List summaries)
sb.AppendLine($" Results saved to: {Path.GetFullPath("./BenchmarkDotNet.Artifacts/results")}");
sb.AppendLine();
sb.AppendLine(" Available formats:");
- sb.AppendLine(" HTML (interactive reports)");
- sb.AppendLine(" CSV (Excel-compatible)");
- sb.AppendLine(" JSON (programmatic access)");
- sb.AppendLine(" Markdown (GitHub-ready)");
+ sb.AppendLine(" � HTML (interactive reports)");
+ sb.AppendLine(" � CSV (Excel-compatible)");
+ sb.AppendLine(" � JSON (programmatic access)");
+ sb.AppendLine(" � Markdown (GitHub-ready)");
sb.AppendLine();
Console.WriteLine(sb.ToString());
diff --git a/SharpCoreDB.Benchmarks/Infrastructure/BenchmarkDatabaseHelper.cs b/SharpCoreDB.Benchmarks/Infrastructure/BenchmarkDatabaseHelper.cs
index 24159a80..043d5be9 100644
--- a/SharpCoreDB.Benchmarks/Infrastructure/BenchmarkDatabaseHelper.cs
+++ b/SharpCoreDB.Benchmarks/Infrastructure/BenchmarkDatabaseHelper.cs
@@ -445,6 +445,15 @@ public void ExecuteBatch(IEnumerable statements)
database.ExecuteBatchSQL(statements);
}
+ ///
+ /// Executes a SQL query and returns the results.
+ /// Used for SELECT, aggregate, and other query operations.
+ ///
+ public List> ExecuteQuery(string sql, Dictionary? parameters = null)
+ {
+ return database.ExecuteQuery(sql, parameters);
+ }
+
public void Dispose()
{
// Database doesn't implement IDisposable, so just clean up service provider
diff --git a/SharpCoreDB.Benchmarks/Program.cs b/SharpCoreDB.Benchmarks/Program.cs
index aa6dbafc..43d66a72 100644
--- a/SharpCoreDB.Benchmarks/Program.cs
+++ b/SharpCoreDB.Benchmarks/Program.cs
@@ -42,6 +42,11 @@
ComprehensiveBenchmarkRunner.Run(args);
return;
+ case "--aggregates":
+ Console.WriteLine("?? Running AGGREGATE benchmarks only...\n");
+ ComprehensiveBenchmarkRunner.Run(args);
+ return;
+
case "--modernization":
Console.WriteLine("?? Running C# 14 modernization benchmarks...\n");
BenchmarkRunner.Run();
@@ -69,6 +74,7 @@ static void ShowHelp()
Console.WriteLine(" --inserts INSERT benchmarks only");
Console.WriteLine(" --selects SELECT benchmarks only");
Console.WriteLine(" --updates UPDATE/DELETE benchmarks only");
+ Console.WriteLine(" --aggregates AGGREGATE benchmarks only");
Console.WriteLine(" --modernization C# 14 modernization benchmarks");
Console.WriteLine(" --help, -h Show this help message");
Console.WriteLine();
@@ -76,13 +82,14 @@ static void ShowHelp()
Console.WriteLine(" dotnet run -c Release -- --quick");
Console.WriteLine(" dotnet run -c Release -- --full");
Console.WriteLine(" dotnet run -c Release -- --inserts");
+ Console.WriteLine(" dotnet run -c Release -- --aggregates");
Console.WriteLine();
Console.WriteLine("Databases compared:");
- Console.WriteLine(" SharpCoreDB (WITH encryption)");
- Console.WriteLine(" SharpCoreDB (WITHOUT encryption)");
- Console.WriteLine(" SQLite (Memory mode)");
- Console.WriteLine(" SQLite (File mode)");
- Console.WriteLine(" LiteDB");
+ Console.WriteLine(" � SharpCoreDB (WITH encryption)");
+ Console.WriteLine(" � SharpCoreDB (WITHOUT encryption)");
+ Console.WriteLine(" � SQLite (Memory mode)");
+ Console.WriteLine(" � SQLite (File mode)");
+ Console.WriteLine(" � LiteDB");
Console.WriteLine();
}
@@ -100,10 +107,11 @@ static void ShowInteractiveMenu()
Console.WriteLine(" 3. INSERT Benchmarks Only");
Console.WriteLine(" 4. SELECT Benchmarks Only");
Console.WriteLine(" 5. UPDATE/DELETE Benchmarks Only");
+ Console.WriteLine(" 6. AGGREGATE Benchmarks Only");
Console.WriteLine();
Console.WriteLine(" Other Benchmarks:");
- Console.WriteLine(" 6. C# 14 Modernization Benchmark");
- Console.WriteLine(" 7. Quick Performance Comparison (no BenchmarkDotNet)");
+ Console.WriteLine(" 7. C# 14 Modernization Benchmark");
+ Console.WriteLine(" 8. Quick Performance Comparison (no BenchmarkDotNet)");
Console.WriteLine();
Console.WriteLine(" H. Help (command-line options)");
Console.WriteLine(" Q. Quit");
@@ -141,10 +149,14 @@ static void ShowInteractiveMenu()
return;
case "6":
- BenchmarkRunner.Run();
+ ComprehensiveBenchmarkRunner.Run(new[] { "--aggregates" });
return;
case "7":
+ BenchmarkRunner.Run();
+ return;
+
+ case "8":
QuickPerformanceComparison.Run();
return;