Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/ci-net11.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 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-preview.1 ]
pull_request:
branches: [ release/v2.1.0.0-preview.1 ]

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'
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"
140 changes: 140 additions & 0 deletions docs/net11/UNION_TYPES_DESIGN.md
Original file line number Diff line number Diff line change
@@ -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<T> : 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<Dictionary<string, object?>> 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.
72 changes: 72 additions & 0 deletions docs/net11/UPGRADE.md
Original file line number Diff line number Diff line change
@@ -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
<TargetFrameworks>net10.0;net11.0</TargetFrameworks>
```

- **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:
<https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-11/runtime>): 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 `<UseRuntimeAsync>false</UseRuntimeAsync>` 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<string> names = Net11Additions.NewList<string>(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.
7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "11.0.100-rc.1.26425.128",
"rollForward": "latestFeature",
"allowPrerelease": true
}
}
11 changes: 6 additions & 5 deletions src/SharpCoreDB/Compression/OptionalCompressionLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,30 @@
namespace SharpCoreDB.Compression;

/// <summary>
/// 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
/// <see cref="System.IO.Compression.CompressionLevel"/> for Brotli/GZip, and on net11.0 to the
/// native Zstandard quality scale (see <c>BlockCompressor.ZstdOptions</c>) for Zstd.
/// </summary>
public enum OptionalCompressionLevel
{
/// <summary>
/// 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).
/// </summary>
Optimal = 0,

/// <summary>
/// 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).
/// </summary>
Fastest = 1,

/// <summary>
/// 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.
/// </summary>
SmallestSize = 3
}
65 changes: 65 additions & 0 deletions src/SharpCoreDB/Net11/Net11Additions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// <copyright file="Net11Additions.cs" company="MPCoreDeveloper">
// 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.
// </copyright>

#if NET11_0_OR_GREATER
namespace SharpCoreDB.Net11;

using System;
using System.Collections.Generic;
using SharpCoreDB.DataStructures;

/// <summary>
/// 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 <c>#if NET11_0_OR_GREATER</c>, so it is compiled ONLY for the
/// net11.0 target — it can never affect .NET 10 consumers, and every member is purely additive.
/// </summary>
public static class Net11Additions
{
/// <summary>
/// Extension indexer: <c>db["tableName"]</c> → the matching <see cref="TableInfo"/> (matched
/// case-insensitively) or <c>null</c> when no table has that name. <see cref="Database"/> has
/// no indexer of its own, so this extension cannot conflict with any existing member.
/// </summary>
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;
}
}
}

/// <summary>
/// Creates an empty <see cref="List{T}"/> pre-sized to <paramref name="capacity"/> using the
/// C# 15 collection-expression argument <c>[with(capacity: …)]</c>, avoiding the reallocation
/// growth of an unseeded list.
/// </summary>
public static List<T> NewList<T>(int capacity) => [with(capacity: capacity)];

/// <summary>
/// Closed-hierarchy demonstration (C# 15 preview <c>closed</c> modifier): the base cannot be
/// instantiated directly and cannot be subclassed outside this assembly, enabling exhaustive
/// dispatch over the known shapes.
/// </summary>
internal closed class ColumnarData
{
internal int ColumnCount { get; init; }
}

internal sealed class FixedWidthData : ColumnarData { }
}
#endif
Loading
Loading