Skip to content

Commit 8e04ddc

Browse files
author
MPCoreDeveloper
committed
refactor(single-file): unify DML/SELECT through shared SqlParser, remove dead regex helpers
- Add ITableFactory-accepting constructor to SqlParser (disambiguated with (IStorage)null! cast) - Rename GetDmlParser() -> GetSqlParser() in SingleFileDatabase - Remove dead [Obsolete] ExecuteInsertInternal and ExecuteUpdateInternal regex helpers - Keep ExecuteCreateTableInternal / ExecuteDropTableInternal regex path (incompatible with Table DDL engine) - Update [Obsolete] messages on public API to accurately reflect current parser split - Rewrite docs/storage/SINGLE_FILE_SQL_LIMITATIONS.md to reflect v1.8 state: DML/SELECT via shared SqlParser (full feature parity with directory mode) DDL via regex path (all common CREATE/DROP TABLE syntax supported)
1 parent fb7866b commit 8e04ddc

22 files changed

Lines changed: 1058 additions & 183 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// <copyright file="M001_InitialSchema.cs" company="MPCoreDeveloper">
2+
// Copyright (c) 2026 MPCoreDeveloper and GitHub Copilot. All rights reserved.
3+
// Licensed under the MIT License.
4+
// </copyright>
5+
6+
using FluentMigrator;
7+
8+
namespace SharpCoreDB.FluentMigratorDemo;
9+
10+
/// <summary>
11+
/// Migration 1: Creates the initial Products and Categories schema.
12+
/// FluentMigrator generates quoted identifiers (e.g. "Products") which
13+
/// SharpCoreDB's single-file parser handles correctly as of v1.7.2.
14+
/// </summary>
15+
[Migration(1, "Initial schema: Products and Categories")]
16+
public sealed class M001_InitialSchema : global::FluentMigrator.Migration
17+
{
18+
/// <inheritdoc />
19+
public override void Up()
20+
{
21+
Create.Table("Categories")
22+
.WithColumn("Id").AsInt64().PrimaryKey().NotNullable()
23+
.WithColumn("Name").AsString(100).NotNullable()
24+
.WithColumn("CreatedAt").AsString(30).NotNullable();
25+
26+
Create.Table("Products")
27+
.WithColumn("Id").AsInt64().PrimaryKey().NotNullable()
28+
.WithColumn("CategoryId").AsInt64().NotNullable()
29+
.WithColumn("Name").AsString(200).NotNullable()
30+
.WithColumn("Price").AsDecimal(18, 2).NotNullable()
31+
.WithColumn("Stock").AsInt32().NotNullable()
32+
.WithColumn("CreatedAt").AsString(30).NotNullable();
33+
}
34+
35+
/// <inheritdoc />
36+
public override void Down()
37+
{
38+
Delete.Table("Products");
39+
Delete.Table("Categories");
40+
}
41+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// <copyright file="M002_SeedData.cs" company="MPCoreDeveloper">
2+
// Copyright (c) 2026 MPCoreDeveloper and GitHub Copilot. All rights reserved.
3+
// Licensed under the MIT License.
4+
// </copyright>
5+
6+
using FluentMigrator;
7+
8+
namespace SharpCoreDB.FluentMigratorDemo;
9+
10+
/// <summary>
11+
/// Migration 2: Seeds initial Categories and Products data.
12+
/// Demonstrates that INSERT with quoted identifiers works correctly
13+
/// in SharpCoreDB's single-file embedded mode.
14+
/// </summary>
15+
[Migration(2, "Seed: initial categories and products")]
16+
public sealed class M002_SeedData : global::FluentMigrator.Migration
17+
{
18+
/// <inheritdoc />
19+
public override void Up()
20+
{
21+
var now = DateTime.UtcNow.ToString("o");
22+
23+
Insert.IntoTable("Categories").Row(new { Id = 1, Name = "Electronics", CreatedAt = now });
24+
Insert.IntoTable("Categories").Row(new { Id = 2, Name = "Books", CreatedAt = now });
25+
26+
Insert.IntoTable("Products").Row(new { Id = 1, CategoryId = 1, Name = "Laptop Pro 15", Price = 1299.99m, Stock = 25, CreatedAt = now });
27+
Insert.IntoTable("Products").Row(new { Id = 2, CategoryId = 1, Name = "Wireless Mouse", Price = 49.99m, Stock = 100, CreatedAt = now });
28+
Insert.IntoTable("Products").Row(new { Id = 3, CategoryId = 2, Name = "Clean Code", Price = 34.95m, Stock = 50, CreatedAt = now });
29+
}
30+
31+
/// <inheritdoc />
32+
public override void Down()
33+
{
34+
Delete.FromTable("Products").AllRows();
35+
Delete.FromTable("Categories").AllRows();
36+
}
37+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// <copyright file="Program.cs" company="MPCoreDeveloper">
2+
// Copyright (c) 2026 MPCoreDeveloper and GitHub Copilot. All rights reserved.
3+
// Licensed under the MIT License.
4+
// </copyright>
5+
6+
// ============================================================
7+
// SharpCoreDB + FluentMigrator — Embedded Single-File Demo
8+
// Demonstrates schema migration on a .scdb database file.
9+
// ============================================================
10+
11+
using FluentMigrator.Runner;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using SharpCoreDB;
15+
using SharpCoreDB.Extensions.Extensions;
16+
using SharpCoreDB.Extensions.Runner;
17+
using SharpCoreDB.FluentMigratorDemo;
18+
19+
PrintHeader();
20+
21+
// ── 1. Create the embedded SharpCoreDB single-file database ──────────────────
22+
var dbPath = Path.Combine(Path.GetTempPath(), $"sharpcoredb_fm_demo_{Guid.NewGuid():N}.scdb");
23+
Console.WriteLine($"Database file: {dbPath}");
24+
Console.WriteLine();
25+
26+
var services = new ServiceCollection();
27+
services.AddSharpCoreDB();
28+
var bootstrapProvider = services.BuildServiceProvider();
29+
var factory = bootstrapProvider.GetRequiredService<DatabaseFactory>();
30+
31+
var dbOptions = DatabaseOptions.CreateSingleFileDefault(enableEncryption: false);
32+
var database = factory.CreateWithOptions(dbPath, masterPassword: "demo-password", dbOptions);
33+
34+
// ── 2. Register SharpCoreDB + FluentMigrator DI ──────────────────────────────
35+
var container = new ServiceCollection();
36+
37+
// Register the IDatabase instance so the FluentMigrator executor can resolve it
38+
container.AddSingleton(database);
39+
40+
// Register SharpCoreDB core services
41+
container.AddSharpCoreDB();
42+
43+
// Register FluentMigrator with the SharpCoreDB processor.
44+
// AddSQLite() registers the SQLite SQL generator that SharpCoreDB uses internally.
45+
// Migrations are discovered from this assembly automatically.
46+
container.AddSharpCoreDBFluentMigrator(runner =>
47+
runner.AddSQLite()
48+
.ScanIn(typeof(M001_InitialSchema).Assembly).For.Migrations());
49+
50+
// Add console logging so migration progress is visible
51+
container.AddLogging(logging =>
52+
{
53+
logging.AddConsole();
54+
logging.SetMinimumLevel(LogLevel.Information);
55+
});
56+
57+
var serviceProvider = container.BuildServiceProvider();
58+
59+
// ── 3. Run MigrateUp — applies all pending migrations ────────────────────────
60+
Console.WriteLine("=== Running Migrations ===");
61+
Console.WriteLine();
62+
63+
var migrationRunner = serviceProvider.GetRequiredService<ISharpCoreDbMigrationRunner>();
64+
migrationRunner.MigrateUp();
65+
66+
Console.WriteLine();
67+
Console.WriteLine("=== Migrations complete ===");
68+
Console.WriteLine();
69+
70+
// ── 4. Query the seeded data ──────────────────────────────────────────────────
71+
Console.WriteLine("=== Querying Products ===");
72+
Console.WriteLine();
73+
74+
var products = database.ExecuteQuery("SELECT * FROM Products");
75+
var categories = database.ExecuteQuery("SELECT * FROM Categories");
76+
var categoryMap = categories.ToDictionary(
77+
r => r["Id"]?.ToString() ?? string.Empty,
78+
r => r["Name"]?.ToString() ?? string.Empty);
79+
80+
Console.WriteLine($"{"Id",-4} {"Product",-25} {"Category",-15} {"Price",10} {"Stock",6}");
81+
Console.WriteLine(new string('-', 65));
82+
83+
foreach (var row in products)
84+
{
85+
var catId = row["CategoryId"]?.ToString() ?? string.Empty;
86+
var catName = categoryMap.TryGetValue(catId, out var cn) ? cn : "?";
87+
Console.WriteLine($"{row["Id"],-4} {row["Name"],-25} {catName,-15} {row["Price"],10} {row["Stock"],6}");
88+
}
89+
90+
Console.WriteLine();
91+
92+
// ── 5. Verify the __SharpMigrations version table ────────────────────────────
93+
Console.WriteLine("=== Applied Migrations ===");
94+
Console.WriteLine();
95+
96+
var appliedMigrations = database.ExecuteQuery("SELECT * FROM __SharpMigrations");
97+
Console.WriteLine($"{"Version",-10} {"Description",-40} {"AppliedOn"}");
98+
Console.WriteLine(new string('-', 75));
99+
100+
foreach (var row in appliedMigrations)
101+
{
102+
Console.WriteLine($"{row["Version"],-10} {row["Description"],-40} {row["AppliedOn"]}");
103+
}
104+
105+
Console.WriteLine();
106+
107+
// ── 7. Cleanup ────────────────────────────────────────────────────────────────
108+
(database as IDisposable)?.Dispose();
109+
110+
try { File.Delete(dbPath); } catch { /* ignore cleanup errors */ }
111+
112+
Console.WriteLine("=== Demo complete ===");
113+
114+
static void PrintHeader()
115+
{
116+
Console.WriteLine("╔══════════════════════════════════════════════════╗");
117+
Console.WriteLine("║ SharpCoreDB + FluentMigrator — Embedded Demo ║");
118+
Console.WriteLine("║ Single-file .scdb with quoted identifiers ║");
119+
Console.WriteLine("╚══════════════════════════════════════════════════╝");
120+
Console.WriteLine();
121+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# SharpCoreDB + FluentMigrator Demo
2+
3+
This example demonstrates using [FluentMigrator](https://fluentmigrator.github.io/) with a **SharpCoreDB embedded single-file database** (`.scdb`).
4+
5+
## What it shows
6+
7+
- Registering `SharpCoreDB` and `FluentMigrator` side-by-side via DI
8+
- Running schema migrations against a `.scdb` file (no server required)
9+
- Handling of **quoted identifiers** — FluentMigrator generates SQL like
10+
`CREATE TABLE IF NOT EXISTS "Products" ("Id" INTEGER NOT NULL, ...)` which SharpCoreDB parses correctly as of v1.7.2
11+
- Rollback and re-apply of migrations
12+
- Querying migrated + seeded data
13+
14+
## Migrations
15+
16+
| Version | Class | Description |
17+
|---------|-------|-------------|
18+
| 1 | `M001_InitialSchema` | Creates `Categories` and `Products` tables |
19+
| 2 | `M002_SeedData` | Inserts initial rows |
20+
21+
## Running
22+
23+
```bash
24+
dotnet run
25+
```
26+
27+
Or from the solution root:
28+
29+
```bash
30+
dotnet run --project Examples/FluentMigrator/SharpCoreDB.FluentMigratorDemo
31+
```
32+
33+
## DI Setup
34+
35+
```csharp
36+
services.AddSingleton(database); // IDatabase — embedded single-file
37+
services.AddSharpCoreDB();
38+
39+
services.AddSharpCoreDBFluentMigrator(runner =>
40+
runner.AddSQLite() // registers the SQLite SQL generator (required)
41+
.ScanIn(typeof(MyMigration).Assembly).For.Migrations());
42+
```
43+
44+
Then resolve and run:
45+
46+
```csharp
47+
var runner = serviceProvider.GetRequiredService<ISharpCoreDbMigrationRunner>();
48+
runner.MigrateUp();
49+
```
50+
51+
## Notes
52+
53+
- The `__SharpMigrations` version table is created automatically by FluentMigrator via the processor — no manual pre-creation needed.
54+
- `StorageMode` is `SingleFile` — no directory, no separate page files.
55+
- Encryption is disabled in this demo for simplicity; enable via `DatabaseOptions.CreateSingleFileDefault(enableEncryption: true, encryptionKey: "...")`.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<LangVersion>14.0</LangVersion>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
<Version>1.7.2</Version>
10+
<IsPackable>false</IsPackable>
11+
<!-- Set to true to use local source projects instead of NuGet packages -->
12+
<UseLocalSharpCoreDbSources Condition="'$(UseLocalSharpCoreDbSources)' == '' and Exists('..\..\..\src\SharpCoreDB\SharpCoreDB.csproj')">true</UseLocalSharpCoreDbSources>
13+
<UseLocalSharpCoreDbSources Condition="'$(UseLocalSharpCoreDbSources)' == ''">false</UseLocalSharpCoreDbSources>
14+
</PropertyGroup>
15+
16+
<!-- Local development: reference source projects directly -->
17+
<ItemGroup Condition="'$(UseLocalSharpCoreDbSources)' == 'true'">
18+
<ProjectReference Include="..\..\..\src\SharpCoreDB\SharpCoreDB.csproj" />
19+
<ProjectReference Include="..\..\..\src\SharpCoreDB.Extensions\SharpCoreDB.Extensions.csproj" />
20+
</ItemGroup>
21+
22+
<!-- NuGet: use published packages -->
23+
<ItemGroup Condition="'$(UseLocalSharpCoreDbSources)' != 'true'">
24+
<PackageReference Include="SharpCoreDB" Version="1.7.2" />
25+
<PackageReference Include="SharpCoreDB.Extensions" Version="1.7.2" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<PackageReference Include="FluentMigrator" Version="8.0.1" />
30+
<PackageReference Include="FluentMigrator.Runner" Version="8.0.1" />
31+
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="8.0.1" />
32+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
33+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.7" />
34+
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="10.0.203" />
35+
</ItemGroup>
36+
37+
</Project>

SharpCoreDB.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.WebViewer", "to
218218
EndProject
219219
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.Viewer.Tests", "tests\SharpCoreDB.Viewer.Tests\SharpCoreDB.Viewer.Tests.csproj", "{3E8BC217-E50D-4B24-8456-A086C0CA0714}"
220220
EndProject
221+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.FluentMigratorDemo", "Examples\FluentMigrator\SharpCoreDB.FluentMigratorDemo\SharpCoreDB.FluentMigratorDemo.csproj", "{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}"
222+
EndProject
221223
Global
222224
GlobalSection(SolutionConfigurationPlatforms) = preSolution
223225
Debug|Any CPU = Debug|Any CPU
@@ -912,6 +914,18 @@ Global
912914
{3E8BC217-E50D-4B24-8456-A086C0CA0714}.Release|x64.Build.0 = Release|Any CPU
913915
{3E8BC217-E50D-4B24-8456-A086C0CA0714}.Release|x86.ActiveCfg = Release|Any CPU
914916
{3E8BC217-E50D-4B24-8456-A086C0CA0714}.Release|x86.Build.0 = Release|Any CPU
917+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
918+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|Any CPU.Build.0 = Debug|Any CPU
919+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|x64.ActiveCfg = Debug|Any CPU
920+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|x64.Build.0 = Debug|Any CPU
921+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|x86.ActiveCfg = Debug|Any CPU
922+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Debug|x86.Build.0 = Debug|Any CPU
923+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|Any CPU.ActiveCfg = Release|Any CPU
924+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|Any CPU.Build.0 = Release|Any CPU
925+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|x64.ActiveCfg = Release|Any CPU
926+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|x64.Build.0 = Release|Any CPU
927+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|x86.ActiveCfg = Release|Any CPU
928+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58}.Release|x86.Build.0 = Release|Any CPU
915929
EndGlobalSection
916930
GlobalSection(SolutionProperties) = preSolution
917931
HideSolutionNode = FALSE
@@ -988,6 +1002,7 @@ Global
9881002
{87E3DF4B-8BF3-45CC-85A0-218418ED54AA} = {A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D}
9891003
{1096939C-2C4C-5BD0-AE71-286857B205CB} = {B2C3D4E5-F6A7-4B8C-9D0E-1F2A3B4C5D6E}
9901004
{3E8BC217-E50D-4B24-8456-A086C0CA0714} = {A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D}
1005+
{8EC903EB-782A-4EB8-AC5B-F45A7A42CD58} = {2F8A8533-DAA8-4CF9-A6C0-2F663AF7FD2E}
9911006
EndGlobalSection
9921007
GlobalSection(ExtensibilityGlobals) = postSolution
9931008
SolutionGuid = {F40825F5-26A1-4E85-9D0A-B0121A7ED5F8}

docs/FEATURE_MATRIX_v1.7.2.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
This page consolidates the major SharpCoreDB capabilities by package for quick discovery.
44

5+
> ⚠️ **Single-File mode SQL limitations**
6+
>
7+
> Features marked below apply to **Directory mode** (`Database` class, `.db` folder) unless otherwise noted.
8+
> `.scdb` single-file mode (`SingleFileDatabase`) uses a **regex-based SQL parser** and does **not** support
9+
> JOIN, GROUP BY, subqueries, aggregates, DELETE without WHERE, and other advanced SQL.
10+
>
11+
> → Full matrix: [`docs/storage/SINGLE_FILE_SQL_LIMITATIONS.md`](storage/SINGLE_FILE_SQL_LIMITATIONS.md)
12+
513
## Core platform
614

715
| Package | Purpose | Key capabilities in v1.7.2 |

docs/USER_MANUAL.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ SharpCoreDB supports multiple storage engines optimized for different workloads:
110110

111111
Default: **PageBased** (switch via `DatabaseOptions`)
112112

113+
> ⚠️ **Single-File mode (`.scdb`) SQL limitations**
114+
>
115+
> When using `.scdb` single-file storage (`StorageMode.SingleFile` / `SingleFileDatabase`),
116+
> SharpCoreDB uses a **regex-based SQL parser** instead of the full query engine.
117+
> Features like JOIN, GROUP BY, subqueries, DELETE without WHERE, and aggregates are **not supported**.
118+
>
119+
> → See [`docs/storage/SINGLE_FILE_SQL_LIMITATIONS.md`](storage/SINGLE_FILE_SQL_LIMITATIONS.md) for the complete matrix.
120+
>
121+
> All SQL features below apply to **Directory mode** (`Database` class, `.db` folder) only.
122+
113123
### Encryption
114124

115125
All data is encrypted at rest with **AES-256-GCM**:
@@ -437,6 +447,8 @@ db.ExecuteQuery("SELECT * FROM users WHERE email IS NOT NULL");
437447

438448
#### JOIN Operations
439449

450+
> ⚠️ **Directory mode only.** JOINs are **not supported** in `.scdb` single-file mode.
451+
440452
```csharp
441453
// INNER JOIN
442454
db.ExecuteQuery(@"

0 commit comments

Comments
 (0)