diff --git a/.github/workflows/ci-net11.yml b/.github/workflows/ci-net11.yml new file mode 100644 index 00000000..0ad4930e --- /dev/null +++ b/.github/workflows/ci-net11.yml @@ -0,0 +1,121 @@ +# CI for the v2.1 preview branch: builds and tests the multi-targeted (net10.0;net11.0) +# core + tests on both target frameworks. Kept separate from ci.yml so the net10-only +# master pipeline is never affected by the .NET 11 RC SDK pin in this branch's global.json. +name: CI v2.1 Preview (.NET 11) + +on: + push: + branches: [ 'release/v2.1.0.0-*' ] + pull_request: + branches: [ 'release/v2.1.0.0-*' ] + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual publish' + required: false + default: 'Manual v2.1 RC publish' + +permissions: + contents: read + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + CI_TEST_FILTER: 'Category!=Debug&Category!=Manual&Category!=Performance' + +jobs: + build-test: + name: Build + Test (${{ matrix.framework }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, windows-latest ] + framework: [ net10.0, net11.0 ] + + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + # The branch's global.json pins the .NET 11 RC SDK, so install both SDKs. The RC SDK + # builds the net10.0 target as well (SDKs are backwards compatible). + - name: Setup .NET 10 + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.0.x' + + - name: Setup .NET 11 (RC) + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '11.0.100-rc.1.26425.128' + include-prerelease: true + + - name: Display .NET info + run: dotnet --info + + - name: Restore dependencies + run: dotnet restore tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj --configfile NuGet.Config + + - name: Build (${{ matrix.framework }}) + run: dotnet build tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj --configuration Release --framework ${{ matrix.framework }} --no-restore + + # MTP (xunit.v3) test hosts no longer support `dotnet test`'s VSTest flags; run the + # MTP executable directly, which understands the same VSTest filter expression and writes TRX. + - name: Test (${{ matrix.framework }}) + shell: bash + run: | + set -euo pipefail + mkdir -p ./TestResults/SharpCoreDB.Tests/${{ matrix.framework }} + tests/SharpCoreDB.Tests/bin/Release/${{ matrix.framework }}/SharpCoreDB.Tests \ + -filterVSTest "${{ env.CI_TEST_FILTER }}" \ + -result-trx "./TestResults/SharpCoreDB.Tests/${{ matrix.framework }}/SharpCoreDB.Tests.trx" + timeout-minutes: 30 + env: + CI: "true" + GITHUB_ACTIONS: "true" + + # Manual publish to NuGet.org (only via workflow_dispatch, using the repo's + # NUGET_API_KEY secret). Packs the net10.0;net11.0 core at the version pinned in the csproj. + publish: + name: Pack + Publish to NuGet.org + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup .NET 10 + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.0.x' + + - name: Setup .NET 11 (RC) + uses: actions/setup-dotnet@v6 + with: + dotnet-version: '11.0.100-rc.1.26425.128' + include-prerelease: true + + - name: Pack SharpCoreDB core + run: dotnet pack src/SharpCoreDB/SharpCoreDB.csproj --configuration Release --output ./artifacts --configfile NuGet.Config + + - name: Push to NuGet.org + shell: bash + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + set -euo pipefail + for pkg in ./artifacts/SharpCoreDB.*.nupkg; do + [ -f "$pkg" ] || continue + echo " Pushing $(basename "$pkg")" + dotnet nuget push "$pkg" \ + --api-key "$NUGET_API_KEY" \ + --source https://api.nuget.org/v3/index.json \ + --skip-duplicate + done diff --git a/docs/net11/UNION_TYPES_DESIGN.md b/docs/net11/UNION_TYPES_DESIGN.md new file mode 100644 index 00000000..ce55155f --- /dev/null +++ b/docs/net11/UNION_TYPES_DESIGN.md @@ -0,0 +1,140 @@ +# Union Types in SharpCoreDB — Design Pass (v2.1 Preview) + +> Branch: `release/v2.1.0.0-preview.1` · Reference: [Union types — C# reference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union) + +## Status + +**Verified available** on .NET 11 RC1 (C# 15 preview): union types work via the `union` keyword and the +`[System.Runtime.CompilerServices.Union]` attribute with `IUnion`. The runtime ships +`UnionAttribute`/`IUnion` since .NET 11 Preview 5. + +**Recommendation:** expose unions as a **new opt-in namespace** `SharpCoreDB.Unions` (net11.0-only), +**never retrofit** existing result types (that would be breaking), and **hold off shipping** until the +preview syntax stabilizes at GA. This document is the ready-to-implement blueprint. + +## Verified syntax (compiles + runs on RC1) + +```csharp +public record class Cat(string Name); +public record class Dog(string Name); + +// Modern union keyword: the compiler generates a `struct : IUnion` with a `Value` property and +// implicit conversions from each case type. +public union Pet(Cat, Dog); + +// Class-based union (reference semantics / private constructors / factories): +[System.Runtime.CompilerServices.Union] +public class Result : System.Runtime.CompilerServices.IUnion +{ + private readonly object? _value; + public Result(T? value) { _value = value; } + public object? Value => _value; +} + +// Exhaustive switch: removing any case below is a COMPILE ERROR. +string Describe(Pet pet) => pet switch +{ + Cat c => $"Cat {c.Name}", + Dog d => $"Dog {d.Name}", +}; +``` + +Key semantics: cases can be classes, structs, interfaces, type parameters, nullable types or other unions; +value-type cases are boxed into `Value`; unions are structs without equality/cloning/deconstruction +(put those on the case types, e.g. `record class` cases). + +## Proposed API: `SharpCoreDB.Unions` (additive, opt-in, net11.0-only) + +```csharp +#if NET11_0_OR_GREATER +namespace SharpCoreDB.Unions; + +using System.Runtime.CompilerServices; +using SharpCoreDB.DataStructures; + +// Query outcome — compile-time exhaustive alternative to returning nullable/boolean-or-throw. +public record class QuerySuccess(IReadOnlyList> Rows); +public record class QueryNotFound; +public record class QueryFailure(string Message); +[Union] +public partial class QueryOutcome : IUnion +{ + private readonly object? _value; + public QueryOutcome(QuerySuccess value) { _value = value; } + public QueryOutcome(QueryNotFound value) { _value = value; } + public QueryOutcome(QueryFailure value) { _value = value; } + public object? Value => _value; +} + +// Execute outcome (rows affected or failure). +public record class ExecuteSuccess(long RowsAffected); +public record class ExecuteFailure(string Message); +[Union] +public partial class ExecuteOutcome : IUnion +{ + private readonly object? _value; + public ExecuteOutcome(ExecuteSuccess value) { _value = value; } + public ExecuteOutcome(ExecuteFailure value) { _value = value; } + public object? Value => _value; +} + +// Table lookup (used with the extension indexer db["name"]). +public record class TableFound(TableInfo Table); +public record class TableMissing(string Name); +[Union] +public partial class TableLookup : IUnion +{ + private readonly object? _value; + public TableLookup(TableFound value) { _value = value; } + public TableLookup(TableMissing value) { _value = value; } + public object? Value => _value; +} +#endif +``` + +Usage (opt-in, only on net11.0): + +```csharp +using SharpCoreDB.Unions; + +QueryOutcome outcome = ...; // returned by NEW opt-in methods only +string text = outcome switch +{ + QuerySuccess s => $"rows: {s.Rows.Count}", + QueryNotFound => "not found", + QueryFailure f => $"error: {f.Message}", +}; +``` + +## Backward compatibility + +- The file is source-gated `#if NET11_0_OR_GREATER` → **compiled only for net11.0**; net10.0 consumers + never see it. +- Existing `QueryResult` / `ExecuteResult` / `ExecuteSQL` signatures stay **byte-identical** on both TFMs. +- Union values can be inspected generically via `IUnion`: + `if (outcome is IUnion { Value: null }) { ... }`. + +## Performance notes + +- Value-type cases are boxed into the `object? Value`. +- Unions are structs; for rich equality/deconstruction keep `record class` case types. +- The `QueryOutcome` design above uses `[Union]` **class** unions (reference semantics) to keep the + API familiar; the `union` keyword equivalent (`public union QueryOutcome(QuerySuccess, QueryNotFound, QueryFailure);`) + is the lighter-weight struct alternative. + +## Test strategy + +1. **Compile-time exhaustiveness** (the core value): a test that switches over `QueryOutcome` with all + cases; add a 4th case type later and watch CI fail until every switch handles it. +2. **Runtime**: construct every case, switch-match it, verify `IUnion.Value` boxing, verify implicit + conversion from each case type. +3. Existing suite stays green: net10.0 1679 / net11.0 1688 (new tests are net11-gated). + +## Risk & decision + +- Union types are still **C# 15 preview**; the implementation (visibility, `Value` boxing, member + requirements) can shift between RC and GA. +- Because existing APIs are untouched and the new surface is opt-in, shipping early is low-risk, but + frozen syntax is higher-value. +- **Decision:** keep `SharpCoreDB.Unions` as a design blueprint for the 2.1 preview; implement it once + .NET 11 GA / final C# 15 confirms the syntax. diff --git a/docs/net11/UPGRADE.md b/docs/net11/UPGRADE.md new file mode 100644 index 00000000..d1723b94 --- /dev/null +++ b/docs/net11/UPGRADE.md @@ -0,0 +1,72 @@ +# SharpCoreDB v2.1 Preview — .NET 11 / C# 15 + +> Branch: `release/v2.1.0.0-preview.1` · Package version: `2.1.0-preview.1` + +## What this is + +The v2.1 preview is built from current `master` and multi-targets: + +```xml +net10.0;net11.0 +``` + +- **net10.0** consumers get exactly the same API and behavior as v2.0 (C# 14, byte-identical, 1679 tests green). +- **net11.0** consumers get the net11-native features below. C# 15 preview language is enabled **only** on this target + (net10.0 stays on C# 14), so nothing about the existing API surface changes. + +## What .NET 11 consumers get automatically + +### 1. Native Zstandard compression +`BlockCompressionMode.Zstd` is now backed by `System.IO.Compression.ZstandardStream` (native zstd): + +- `OptionalCompressionLevel` maps to zstd quality: `Fastest → 1`, `Optimal → 5`, `SmallestSize → 19`. +- A frame checksum is appended (`ZstandardCompressionOptions.AppendChecksum = true`), so corruption is + detected on read. +- On **net10.0**, `Zstd` throws `PlatformNotSupportedException` (documented in `BlockCompressionMode`). + +### 2. Runtime Async +The net11.0 target compiles with `Features=runtime-async=on` (official reference: +): async/await emits +**runtime-native state machines** instead of per-method generated ones — cleaner stack traces, better +debuggability, lower overhead. No API or behavior change. The net10.0 target is untouched. +To opt a single project out, set `false` in its project file +(the old `DOTNET_RuntimeAsync` / `UNSUPPORTED_RuntimeAsync` environment variables are removed). + +### 3. C# 15 preview API surface (`SharpCoreDB.Net11`, opt-in) +Import `using SharpCoreDB.Net11;` to unlock additive members: + +```csharp +using SharpCoreDB.Net11; + +TableInfo? docs = db["docs"]; // extension indexer → TableInfo? (case-insensitive) +List names = Net11Additions.NewList(128); // collection-expression capacity argument +``` + +- The extension indexer cannot conflict with existing members: `Database` has no indexer of its own. +- These members are source-gated (`#if NET11_0_OR_GREATER`) and compiled only for net11.0. + +## What is deliberately NOT in this preview + +| Item | Reason | +|---|---| +| **Union types** | Verified working in RC1 (`union` keyword + `[Union]`/`IUnion`); blueprint ready in `docs/net11/UNION_TYPES_DESIGN.md`. Held back from shipping until the preview syntax stabilizes at GA; will live in a new opt-in `SharpCoreDB.Unions` namespace (never retrofitted onto existing result types). | +| **SIMD Zip / Unzip / Concat / CreateGeometricSequence** | Not applied yet — needs benchmarking against the existing `SimdHelper` AVX-512 → AVX2 → SSE/Neon kernels first. | +| **Broad `[with(capacity: …)]` rewrites** | Would require `#if` gating across shared hot-path code; only exposed via the net11-only helper for now. | + +None of the above touches the net10.0 target. + +## Backward compatibility guarantees + +- No existing member changed; only additive members were introduced. +- net10.0: 1679 tests green (unchanged). +- net11.0: 1688 tests green, including Zstd round-trip, storage compression/reopen, and the new net11 tests. + +## Consuming the preview + +``` +git fetch origin release/v2.1.0.0-preview.1 +git checkout release/v2.1.0.0-preview.1 +dotnet pack src/SharpCoreDB/SharpCoreDB.csproj -c Release +``` + +The `.nupkg` carries both `net10.0` and `net11.0` assets; NuGet picks the appropriate one per consuming project. diff --git a/global.json b/global.json new file mode 100644 index 00000000..08788258 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "11.0.100-rc.1.26425.128", + "rollForward": "latestPatch", + "allowPrerelease": true + } +} diff --git a/src/SharpCoreDB/Compression/OptionalCompressionLevel.cs b/src/SharpCoreDB/Compression/OptionalCompressionLevel.cs index ffdd508f..ed2945f1 100644 --- a/src/SharpCoreDB/Compression/OptionalCompressionLevel.cs +++ b/src/SharpCoreDB/Compression/OptionalCompressionLevel.cs @@ -5,29 +5,30 @@ namespace SharpCoreDB.Compression; /// -/// Compression level presets for Brotli and GZip streaming compression. -/// Maps to System.IO.Compression.CompressionLevel with trade-offs between CPU cost and compression ratio. +/// Compression level presets for streaming compression. Maps to +/// for Brotli/GZip, and on net11.0 to the +/// native Zstandard quality scale (see BlockCompressor.ZstdOptions) for Zstd. /// public enum OptionalCompressionLevel { /// /// Balanced preset: good compression ratio with reasonable CPU cost. /// Recommended for data blocks where storage efficiency matters. - /// Maps to CompressionLevel.Optimal. + /// Maps to CompressionLevel.Optimal (Zstd quality 5). /// Optimal = 0, /// /// Fastest preset: minimal CPU cost, larger output size. /// Recommended for metadata where write speed is critical and data is small. - /// Maps to CompressionLevel.Fastest. + /// Maps to CompressionLevel.Fastest (Zstd quality 1). /// Fastest = 1, /// /// Best compression preset: maximum ratio, highest CPU cost. /// Recommended for offline archival or cold storage workloads. - /// Maps to CompressionLevel.SmallestSize (.NET 10+). + /// Maps to CompressionLevel.SmallestSize (.NET 10+); Zstd quality 19 on net11.0. /// SmallestSize = 3 } diff --git a/src/SharpCoreDB/Net11/Net11Additions.cs b/src/SharpCoreDB/Net11/Net11Additions.cs new file mode 100644 index 00000000..9a10a5e3 --- /dev/null +++ b/src/SharpCoreDB/Net11/Net11Additions.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) 2026 MPCoreDeveloper and GitHub Copilot. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +#if NET11_0_OR_GREATER +namespace SharpCoreDB.Net11; + +using System; +using System.Collections.Generic; +using SharpCoreDB.DataStructures; + +/// +/// Additive, net11.0-only API surface demonstrating the backward-compatible C# 15 preview +/// features: extension indexers, closed hierarchies and collection-expression capacity arguments. +/// This file is source-gated via #if NET11_0_OR_GREATER, so it is compiled ONLY for the +/// net11.0 target — it can never affect .NET 10 consumers, and every member is purely additive. +/// +public static class Net11Additions +{ + /// + /// Extension indexer: db["tableName"] → the matching (matched + /// case-insensitively) or null when no table has that name. has + /// no indexer of its own, so this extension cannot conflict with any existing member. + /// + extension(Database db) + { + public TableInfo? this[string tableName] + { + get + { + ArgumentException.ThrowIfNullOrEmpty(tableName); + foreach (var info in db.GetTables()) + { + if (string.Equals(info.Name, tableName, StringComparison.OrdinalIgnoreCase)) + { + return info; + } + } + + return null; + } + } + } + + /// + /// Creates an empty pre-sized to using the + /// C# 15 collection-expression argument [with(capacity: …)], avoiding the reallocation + /// growth of an unseeded list. + /// + public static List NewList(int capacity) => [with(capacity: capacity)]; + + /// + /// Closed-hierarchy demonstration (C# 15 preview closed modifier): the base cannot be + /// instantiated directly and cannot be subclassed outside this assembly, enabling exhaustive + /// dispatch over the known shapes. + /// + internal closed class ColumnarData + { + internal int ColumnCount { get; init; } + } + + internal sealed class FixedWidthData : ColumnarData { } +} +#endif \ No newline at end of file diff --git a/src/SharpCoreDB/Services/BlockCompressor.cs b/src/SharpCoreDB/Services/BlockCompressor.cs index 3843ad4c..a85bf4b1 100644 --- a/src/SharpCoreDB/Services/BlockCompressor.cs +++ b/src/SharpCoreDB/Services/BlockCompressor.cs @@ -28,8 +28,7 @@ public static byte[] Compress( if (mode == BlockCompressionMode.None) return data.ToArray(); using var output = new MemoryStream(data.Length / 2); - var compressionLevel = ToBcl(level); - using (var compressor = CreateCompressor(output, mode, compressionLevel)) + using (var compressor = CreateCompressor(output, mode, level)) { compressor.Write(data); } @@ -51,12 +50,12 @@ public static byte[] Decompress(ReadOnlySpan data, BlockCompressionMode mo return output.ToArray(); } - private static Stream CreateCompressor(Stream output, BlockCompressionMode mode, CompressionLevel level) => mode switch + private static Stream CreateCompressor(Stream output, BlockCompressionMode mode, OptionalCompressionLevel level) => mode switch { - BlockCompressionMode.Brotli => new BrotliStream(output, level, leaveOpen: false), - BlockCompressionMode.GZip => new GZipStream(output, level, leaveOpen: false), + BlockCompressionMode.Brotli => new BrotliStream(output, ToBcl(level), leaveOpen: false), + BlockCompressionMode.GZip => new GZipStream(output, ToBcl(level), leaveOpen: false), #if NET11_0_OR_GREATER - BlockCompressionMode.Zstd => new ZstandardStream(output, level, leaveOpen: false), + BlockCompressionMode.Zstd => new ZstandardStream(output, ZstdOptions(level), leaveOpen: false), #else BlockCompressionMode.Zstd => throw new PlatformNotSupportedException( "Zstd compression requires .NET 11 or later. Current runtime: " + Environment.Version), @@ -87,4 +86,23 @@ private static CompressionLevel ToBcl(OptionalCompressionLevel level) => OptionalCompressionLevel.SmallestSize => CompressionLevel.SmallestSize, _ => CompressionLevel.Optimal }; + +#if NET11_0_OR_GREATER + /// + /// Builds Zstandard compression options for the net11.0 native Zstandard path. + /// is mapped onto Zstandard's quality scale (1 = fastest, + /// 22 = best ratio), and a frame checksum is appended so corruption is detected on read. + /// + private static ZstandardCompressionOptions ZstdOptions(OptionalCompressionLevel level) => + new() + { + Quality = level switch + { + OptionalCompressionLevel.Fastest => 1, + OptionalCompressionLevel.SmallestSize => 19, + _ => 5, // Optimal: balanced, above zstd's default level 3 + }, + AppendChecksum = true, + }; +#endif } \ No newline at end of file diff --git a/src/SharpCoreDB/SharpCoreDB.csproj b/src/SharpCoreDB/SharpCoreDB.csproj index 8b47b3c9..bf4ff300 100644 --- a/src/SharpCoreDB/SharpCoreDB.csproj +++ b/src/SharpCoreDB/SharpCoreDB.csproj @@ -1,8 +1,16 @@ - net10.0 - 14.0 + net10.0;net11.0 + + preview + 14.0 + + $(Features);runtime-async=on enable enable true @@ -31,16 +39,16 @@ SharpCoreDB - 2.0.0.2 - 2.0.0.2 - 2.0.0.2 + 2.1.0-RC.1 + 2.1.0.0 + 2.1.0.0 MPCoreDeveloper SharpCoreDB SharpCoreDB A lightweight, encrypted, file-based database engine with SQL support, AES-256-GCM encryption, modern C# features, and production-ready performance for time-tracking, invoicing, and project management applications. Supports Windows, Linux, macOS, Android, iOS, and IoT/embedded devices. Copyright (c) 2026 MPCoreDeveloper - v2.0.0.2 (2026-09-04): 2.x performance-first engine line - fixed-width record default for new PK tables, in-place UPDATE/DELETE with commit-time tombstones, and reopen data-integrity hardening (empty-value overflow reload, single-file fixed-width arena markers, legacy delete purge). Full details: https://github.com/MPCoreDeveloper/SharpCoreDB/blob/master/docs/CHANGELOG.md - database;embedded;encryption;sql;nosql;time-tracking;invoicing;net10;csharp14;performance;android;ios;mobile;iot;arm64;x64;distributed;sync + v2.1.0-RC.1 (2026-09-12): .NET 11 / C# 15 preview release line — multi-targets net10.0;net11.0, native Zstandard compression (quality presets 1/5/19 + frame checksum), Runtime Async on net11, and an additive C# 15 API surface (SharpCoreDB.Net11). Fully backward compatible with v2.0 on the net10.0 target. See docs/net11/UPGRADE.md for details. + database;embedded;encryption;sql;nosql;time-tracking;invoicing;net10;net11;csharp14;csharp15;zstd;performance;android;ios;mobile;iot;arm64;x64;distributed;sync MIT https://github.com/MPCoreDeveloper/SharpCoreDB https://github.com/MPCoreDeveloper/SharpCoreDB diff --git a/src/SharpCoreDB/global.json b/src/SharpCoreDB/global.json index c6cbb9a0..bdecd288 100644 --- a/src/SharpCoreDB/global.json +++ b/src/SharpCoreDB/global.json @@ -1,5 +1,7 @@ { "sdk": { - "version": "10.0.100" + "version": "11.0.100-rc.1.26425.128", + "rollForward": "latestPatch", + "allowPrerelease": true } } \ No newline at end of file diff --git a/tests/SharpCoreDB.Tests/Net11/Net11AdditionsTests.cs b/tests/SharpCoreDB.Tests/Net11/Net11AdditionsTests.cs new file mode 100644 index 00000000..4c88cabf --- /dev/null +++ b/tests/SharpCoreDB.Tests/Net11/Net11AdditionsTests.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2026 MPCoreDeveloper and GitHub Copilot. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +#if NET11_0_OR_GREATER +namespace SharpCoreDB.Tests.Net11; + +using SharpCoreDB; +using SharpCoreDB.Net11; +using Xunit; + +/// +/// Tests for the additive net11.0-only C# 15 preview API surface +/// (). These tests run only on the net11.0 target. +/// +public sealed class Net11AdditionsTests : IDisposable +{ + private readonly string testDbPath; + private readonly Database db; + + public Net11AdditionsTests() + { + testDbPath = Path.Combine(Path.GetTempPath(), $"net11_additions_{Guid.NewGuid()}"); + Directory.CreateDirectory(testDbPath); + + var config = DatabaseConfig.Benchmark; + db = new Database( + Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions + .BuildServiceProvider(new Microsoft.Extensions.DependencyInjection.ServiceCollection().AddSharpCoreDB()), + testDbPath, + "test_password", + isReadOnly: false, + config: config); + } + + public void Dispose() + { + try { db.Dispose(); } catch { } + + // Force garbage collection to release file handles + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + try + { + if (Directory.Exists(testDbPath)) + { + Directory.Delete(testDbPath, recursive: true); + } + } + catch { } + } + + [Fact] + public void ExtensionIndexer_ReturnsTableInfo_ForExistingTable() + { + db.ExecuteSQL("CREATE TABLE docs (id INTEGER PRIMARY KEY, name TEXT)"); + + var info = db["docs"]; + + Assert.NotNull(info); + Assert.Equal("docs", info!.Name); + } + + [Fact] + public void ExtensionIndexer_ReturnsNull_ForUnknownTable() + { + var info = db["does_not_exist"]; + + Assert.Null(info); + } + + [Fact] + public void NewList_PresizesCapacity() + { + var list = Net11Additions.NewList(64); + + Assert.Empty(list); + Assert.Equal(64, list.Capacity); + } +} +#endif \ No newline at end of file diff --git a/tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj b/tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj index 6e375db1..69d81757 100644 --- a/tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj +++ b/tests/SharpCoreDB.Tests/SharpCoreDB.Tests.csproj @@ -1,9 +1,10 @@ - net10.0 - 2.0.0.2 - 14.0 + net10.0;net11.0 + 2.1.0-RC.1 + preview + 14.0 enable enable false